2010-04-14 36 views
2

我有一組日誌和調試功能,我想跨多個模塊/對象使用。我希望能夠使用命令行開關全局地打開/關閉它們。從Perl模塊導入有條件編譯的函數

下面的代碼是這樣做的,但是,我希望能夠省略軟件包名稱並將所有內容保存在單個文件中。

更具體地說,我想日誌函數的名稱導入到各模塊,讓他們可以在沒有任何包名資格(類似於C++ use namespace;指令)被調用,我希望能夠啓用/禁用它們全局使用它們的腳本,如下面的示例代碼所示。

這與我詢問的兩個先前問題herehere有關。

另一件事 - 我不認爲我完全理解爲什麼下面的代碼工作。

#! /usr/bin/perl -w 

use strict; 
use Getopt::Long; 

{ 
    package LogFuncs; 

    use threads; 
    use Time::HiRes qw(gettimeofday); 

    # provide tcpdump style time stamp 
    sub _gf_time { 
     my ($seconds, $microseconds) = gettimeofday(); 
     my @time = localtime($seconds); 

     return sprintf("%02d:%02d:%02d.%06ld", 
      $time[2], $time[1], $time[0], $microseconds); 
    } 

    sub logerr; 

    sub compile { 
     my %params = @_; 

     *logerr = $params{do_logging} 
      ? sub { 
       my $msg = shift; 
       warn _gf_time() . " Thread " . threads->tid() . ": $msg\n"; 
      } 
      : sub { }; 
    }    
} 

{ 
    package FooObj; 

    sub new { 
     my $class = shift; 
     bless {}, $class; 
    }; 

    sub foo_work { 
     my $self = shift; 
     # do some foo work 
     LogFuncs::logerr($self); 
    } 
} 

{ 
    package BarObj; 

    sub new { 
     my $class = shift; 
     my $data = { fooObj => FooObj->new() }; 
     bless $data, $class; 
    } 

    sub bar_work { 
     my $self = shift; 
     $self->{fooObj}->foo_work(); 
     LogFuncs::logerr($self); 
    } 
}   

my $do_logging = 0; 

GetOptions(
    "do_logging" => \$do_logging, 
); 

LogFuncs::compile(do_logging => $do_logging); 

my $bar = BarObj->new(); 
LogFuncs::logerr("Created $bar"); 
$bar->bar_work(); 

回答

6

如果你想保留所有文件在同一個文件中,爲什麼不把logger放在文件範圍內的頂部詞彙。

GetOptions(do_logging => \$do_logging); 

my $logerr = $do_logging ? sub {logging_code_here()} : sub {}; 

$logerr現在可以在同一個文件在該點之後定義的任何包。

但是,它的速度通常較快構建你的日誌調用如下:

my $logerr = sub { logging_code_here() }; 

$logerr->("some string $some_var") if $do_logging; 

你避免子程序調用和字符串參數$ LOGERR並不需要計算,如果日誌記錄是關閉的方式。

您還可以設置日誌記錄級別:

$logerr->("starting loop")  if $do_logging; 

for (@big_array) { 
    $logerr->("processing $_") if $do_logging > 1; 
    ... 
} 

編輯:雖然我不認爲它的最佳實踐,根據您的意見,這裏是你可能尋找(編譯指示):

use 5.010; 
use warnings; 
use strict; 
BEGIN { # compile time 
    $INC{'log.pm'}++; # block 'require log;' 
    package log; 
    sub is_active {(caller 1)[10]{log}} # test the hints hash 
    sub import { 
     $^H{log} = 1; # set the hints hash for 'log' 
     my $logmsg = (caller).'::logmsg'; # name of caller's sub 
     no strict 'refs'; 
     *$logmsg = sub {print "logging: @_\n" if is_active} # install sub 
      unless *{$logmsg}{CODE}; # unless we did already 
    } 
    sub unimport { 
     $^H{log} = 0; # unset the hints hash 
    } 
} 

package MyPkg; 

use log; 
logmsg 'hello, world!'; 

{ 
    no log; 
    logmsg 'nope, not here'; 
} 

logmsg 'back again'; 
+0

@Eric Strom:但後來我不需要引用包主要將它們拉入我的對象包?和'main :: $ logerr - >(「processing $ _」)一樣,如果$ do_logging> 1;'。整個問題是我試圖簡潔並減少代碼中的打字量和混亂程度。對於不同的日誌/調試級別,我只是有不同的功能,即'loginfo1','loginfo2'等等。我認爲Perl足夠聰明,可以在日誌記錄關閉時優化掉空函數的調用。謝謝,但我認爲你的解決方案不符合我的要求。 – 2010-04-15 06:32:23

+0

@Robert S. Barnes =>不,這不是'my'變量的範圍。詞法不是包的成員,而是作用域級別的成員,因此如果在文件的根作用域中定義了一個詞法變量,它將在同一文件中的每個包下面的程序包/塊中可見。另外,我不認爲perl會優化子呼叫,並且在子呼叫之前計算消息文本的速度可能很慢(例如,如果日誌代碼處於緊密的內部循環中)。通過將檢查外的子檢查移動到調用代碼中,可以防止在調試級別0進行大量不必要的計算。 – 2010-04-15 07:10:55

+0

@Eric Strom:感謝您對可見性的澄清。但是,當我嘗試將日誌函數放在全局範圍內時,它在下面的包中不可見。你是否說我的變量是可見的,而沒有我的聲明的變量不會?關於一方與另一方的表現,唯一真正知道的方法就是測試它。 – 2010-04-15 07:38:10

2

如果你需要修補一些測井方法(或做其他事有條件的開發環境與生產),你可以把代碼放到一個包裝的import()方法和使用參數在use線啓用/禁用它:

# this is in MyLogger.pm: 
package MyLogger; 

use strict; use warnings; 
use Exporter; 

our @EXPORT_OK = qw(logerr); 

sub import 
{ 
    my ($class, $debug) = @_; 

    if ($debug) 
    { 
      # do whatever you need here - patch methods, turn up the log level to "everything", etc 

      # imports logerr method 
      MyLogger->export_to_level(1, 'logger'); 
    } 
} 

sub logerr 
{ 
    # your patched method here.. 
} 

1; 

# (now in your application code...) 
use MyLogger "debug"; # enables debugging mode 
# or: 
use MyLogger MyConfig::DEBUG; # uses a debugging constant that you defined elsewhere 
+0

@Ether:這聽起來很有希望。這是否允許我在使用各種模塊的腳本中全局打開/關閉日誌記錄?如果所有模塊都在單個文件中定義,這將如何工作?你能根據我在帖子中提供的代碼給出更完整的例子嗎? – 2010-04-15 06:37:36

+0

我不得不說,這比Eric Strom的想法好得多。 – 2010-04-15 15:26:06

+0

@Kinopiko:我只是不完全確定它是如何工作的。 – 2010-04-15 15:51:40

2

下面你將你的代碼沒有明確包的日誌記錄功能。我已經將LogFuncs :: compile重命名爲logerr:setup更清晰一些,並且不會污染主命名空間。 perl中的所有東西都有一個包,默認情況下它只是主要。我認爲你之前使用的包含日誌記錄功能的代碼實際上是更清晰的,並且可以很容易地將它分割成單獨的模塊,因此可以更容易地重用它。至於不理解代碼是如何工作的,我不知道如何解決這個問題,但僅僅是因爲你沒有確定任何你不明白的細節。我會假設你是指包裹。 Perl包可以被視爲名稱空間或類。對於LogFuncs軟件包,您將其用作命名空間,並且在其中創建的潛水艇將作爲靜態類方法進行訪問(以使用其他更傳統的面向對象的傳統OO語言的某些表示法)。包FooObj和BarObj通過提供構造函數new()更傳統上用作對象,並期望使用對象表示法( - > sub)調用其中的子對象,自動將對象本身作爲第一個參數傳遞給子對象/方法。將sub定義爲靜態類方法或對象方法的唯一方法是是否期望接收該對象作爲第一個參數。因此,通過正確地確定第一個傳遞的參數是什麼並且相應地執行,一些小心的預見和仔細的編程可以生成對兩者都有效的子程序。

#! /usr/bin/perl -w 

use strict; 
use Getopt::Long; 

use threads; 
use Time::HiRes qw(gettimeofday); 

# provide tcpdump style time stamp 
sub _gf_time { 
my ($seconds, $microseconds) = gettimeofday(); 
my @time = localtime($seconds); 

return sprintf("%02d:%02d:%02d.%06ld", 
    $time[2], $time[1], $time[0], $microseconds); 
} 

sub logerr; 

sub logerr_setup { 
    my %params = @_; 

    *logerr = $params{do_logging} 
     ? sub { 
      my $msg = shift; 
      warn _gf_time() . " Thread " . threads->tid() . ": $msg\n"; 
     } 
     : sub { }; 
    } 

{ 
    package FooObj; 

    sub new { 
     my $class = shift; 
     bless {}, $class; 
    }; 

    sub foo_work { 
     my $self = shift; 
     # do some foo work 
     main::logerr($self); 
    } 
} 

{ 
    package BarObj; 

    sub new { 
     my $class = shift; 
     my $data = { fooObj => FooObj->new() }; 
     bless $data, $class; 
    } 

    sub bar_work { 
     my $self = shift; 
     $self->{fooObj}->foo_work(); 
     main::logerr($self); 
    } 
} 

my $do_logging = 0; 

GetOptions(
    "do_logging" => \$do_logging, 
); 

logerr_setup(do_logging => $do_logging); 

my $bar = BarObj->new(); 
logerr("Created $bar"); 
$bar->bar_work(); 
+0

@kbenson:+1的解釋。實際上,我想要的是相當於C++'use namespace;'指令。我想將日誌記錄功能保留在它們自己的包中,能夠直接引用它們而不需要**任何**包名,並在主腳本中全局打開/關閉它們。現在我已經有三分之二了。 – 2010-04-15 07:00:14

+0

@Robert S. Barnes:看看出口商的perldoc,我想這就是你想要的。它允許您定義關鍵字,用於導出部分或全部包名稱空間,或者自動導出一些部分。當你看到像Time :: HiRes'time'這樣的使用語法來覆蓋內置的time()以返回hi分辨率時,這就是使用的內容。它將Time :: HiRes命名空間中定義的新time()子導出到main中。 – kbenson 2010-04-15 23:16:08