Log4net(五)— Appenders(记录方式)

Monday, April 30, 2007

 

原文地址:Appenders

 

选择和禁止记录请求只是log4net框架的一部份。log4net允许将记录请求输出到不同的目的地。在log4net中,输出目的地叫做Appender(记录方式)。记录方式必须继承log4net.Appenders.IAppender接口。

log4net中定义了下面的记录方式: (链接到英文的log4net SDK)

类型

描述

log4net.Appender.AdoNetAppender

把记录通过SQL语句或者存储过程保存到数据库中

log4net.Appender.AnsiColorTerminalAppender

把记录进行颜色高亮,并输出到ANSI终端窗口

log4net.Appender.AspNetTraceAppender

将记录输出到ASP追踪页面,他们被返回到ASP页面或者追踪页面的底部。

log4net.Appender.BufferingForwardingAppender

缓存送到子记录器的记录

log4net.Appender.ColoredConsoleAppender

把记录输出到程序的控制台。事件信息可以输出到标准输出流,也可以输出到标准错误流。事件可以包含为每个等级定义的结构化文本和背景颜色。

log4net.Appender.ConsoleAppender

把记录输出到程序的控制台。事件信息可以输出到标准输出流,也可以输出到标准错误流。

log4net.Appender.EventLogAppender

把记录输出到Windows事件日志

log4net.Appender.FileAppender

把记录输出到文件系统的文件中

log4net.Appender.ForwardingAppender

把记录送到子记录器

log4net.LocalSyslogAppender

把记录输出到本地系统日志服务(UNIX)

log4net.Appender.MemoryAppender

把记录保存到内存缓存中

log4net.Appender.NetSendAppender

把时间输出到Windows消息服务,这些消息在用户终端中显示

log4net.Appender.OutputDebugStringAppender

把记录输出到调试器。如果程序没有调试器,将会输出到系统调试器。如果系统没有调试器并且系统调试器被禁用,记录被忽略

log4net.Appender.RemoteSyslogAppender

把记录通过UDP网络输出到远程系统记录服务。

log4net.Appender.RemotingAppender

通过.NET Remoting把记录输出到远程接收器

log4net.Appender.RollingFileAppender

把记录输出到文件系统的文件中。RollingFileAppender可以通过配置日期或者文件大小的限制把记录配置输出到多个文件。

log4net.Appender.SmtpAppender

把记录发送到制定邮箱地址

log4net.Appender.SmtpPickupDirAppender

把SMTP消息记录到制定目录的文件中,这些文件可已通过像IIS SMTP代理这样的SMTP代理读取和发送

log4net.Appender.TelnetAppender

用户通过Telnet连接来获取记录

log4net.Appender.TraceAppender

把记录输出到.NET追踪系统

log4net.Appender.UdpAppender

把记录当成无状态UDP数据包发送到使用UDP客户端的远程主机或者组。

一个记录器能够使用道中输出方式。

每条记录器允许的记录请求将被输出到记录器所有的记录方式,包括记录器层次结构中上级的记录器的记录方式。换句话说,记录方式具备继承加成的特性。例如,如果在根控制器上增加了一个控制台记录方式,那么所有被允许的记录请求将至少被输出到控制台。如果记录器增加了一个文件记录方式X,所有X和X的后代通过的记录请求将被输出到文件和控制台。可以将记录器的additivity属性标记为false来避免记录方式的集成加成特性。

下面总结了记录方式的加成特性。

记录器X的记录语句将会输出到X和他祖先的所有记录方式。这就是“继承加成”

但是,如果X的祖先Y,设置了additivity属性为false,那么X的的记录将会输出到X到Y(包括X和Y)之间的所有记录方式,但不会输出到Y的祖先的记录方式。

记录器的默认additivity属性为true。

下面是一个例子

记录器名

记录方式

additivity属性

输出目标

备注

root

A1

不适用

A1

root没有继承任何输出方式

x

A-x1, A-x2

true

A1, A-x1, A-x2

x和root的记录方式

x.y

none

true

A1, A-x1, A-x2

x和root的记录方式

x.y.z

A-xyz1

true

A1, A-x1, A-x2, A-xyz1

x, x.y.z和root的记录方式

security

A-sec

false

A-sec

因为additivity属性为false,没有任何记录方式累加

security.access

none

true

A-sec

因为security的additivity属性为false,因此只有security的记录方式

......

[阅读全文]

log4net(四) — Logger(记录器)

Sunday, April 29, 2007

前三节主要翻译和学习了配置文件的写法。从这一节将计划翻译Logger(第四节)、记录方式(第五节)、布局(第六节)和过滤器(第七节)。

原文地址:Loggers and Appenders

log4net主要包含三个主要部分,记录器(loggers)、记录方式(appenders)和布局(layouts)。通过这三个部件,开发者可以根据消息类型和等级记录消息,并在运行时刻控制消息的格式和输入方式。这些部件使用过滤器(filters)控制记录方式的行为,使用renderers把对象转换为字符串。

Logger hierarchy(记录器层次结构)

使用记录API相比System.Console.WriteLine最大的优势在于可以禁用某些特定消息并让其他消息畅通无阻。它把所有可能记录的消息按开发者规定的元素进行归类。

记录器是已命名的实体。名字大小写敏感并且遵守夏磊层次命名规则:

如果记录器A的名字以其他记录器B的名字加上.为前缀,那么它就是其他记录器的后代(A是B的后代)。记录器被称为他直接子记录器的父亲。

这个层次结构和.NET中的命名空间、类层次结构类似。在下面我们可以看见这样将非常方便。

例如:名为"Foo.Bar"的记录器是"Foo.Bar.Baz"记录器的父亲。类似的,”System”是"System.Text"的父亲和"System.Text.StringBuilder"的祖先。这种命名方式对于程序来说是相当熟悉的。

根记录器(root logger)位于层次结构的顶端。它在以下三方面表现出特别之处。

1. 它总是存在的。

2. 他不能通过名字获取。

3. 它总被指定在某个等级。

记录器可以通过log4net.LogManger类的静态方法获得。GetLogger方法以欲获取的记录器名称为参数,如下所示:

namespace log4net
{
    public class LogManager
    {
        public static ILog GetLogger(string name);
        public static ILog GetLogger(Type type);
    }
}

GetLogger方法也可以接受欲获取记录器的完整类型为名称的Type类型参数。

记录器返回ILog接口,它代表了返回给开发者的记录器。ILog接口定义如下

namespace log4net
{
    public interface ILog
    {
        /* Test if a level is enabled for logging */
        bool IsDebugEnabled { get; }
        bool IsInfoEnabled { get; }
        bool IsWarnEnabled { get; }
        bool IsErrorEnabled { get; }
        bool IsFatalEnabled { get; }
        
        /* Log a message object */
        void Debug(object message);
        void Info(object message);
        void Warn(object message);
        void Error(object message);
        void Fatal(object message);
        
        /* Log a message object and exception */
        void Debug(object message, Exception t);
        void Info(object message, Exception t);
        void Warn(object message, Exception t);
        void Error(object message, Exception t);
        void Fatal(object message, Exception t);
        
        /* Log a message string using the System.String.Format syntax */
        void DebugFormat(string format, params object[] args);
        void InfoFormat(string format, params object[] args);
        void WarnFormat(string format, params object[] args);
        void ErrorFormat(string format, params object[] args);
        void FatalFormat(string format, params object[] args);
        
        /* Log a message string using the System.String.Format syntax */
        void DebugFormat(IFormatProvider provider, string format, params object[] args);
        void InfoFormat(IFormatProvider provider, string format, params object[] args);
        void WarnFormat(IFormatProvider provider, string format, params object[] args);
        void ErrorFormat(IFormatProvider provider, string format, params object[] args);
        void FatalFormat(IFormatProvider provider, string format, params object[] args);
    }
}

记录器可以被指定等级。等级是log4net.Core.Level类的实例。下列等级依照递增优先级被定义。

ALL DEBUG INFO WARN ERROR FATAL OFF

如果一个记录器没有被指定等级,那么它继承它最近的父类的等级。

未指定等级的记录器X 继承 从X向上到根记录器的路线中第一个被直接指定等级的记录器的等级。

为了保证所有的记录器能够继承一个等级,所以根记录器总被指定一个等级,默认为DEBUG。下面4个表格是根据上面规则分配和继承等级的例子。

记录器名

被指定等级

继承等级

root

Proot

Proot

X

none

Proot

X.Y

none

Proot

X.Y.Z

none

proot

上面例子中只有根记录器被指定了等级Proot,因此其他记录器X, X.Y, X.Y.Z均继承了等级Proot。

记录器名

被指定等级

继承等级

root

Proot

Proot

X

Px

Px

X.Y

Pxy

Pxy

X.Y.Z

Pxyz

pxyz

上面的例子中,每个记录器都被指定了等级,因此没有继承的等级。

记录器名

被指定等级

继承等级

root

Proot

Proot

X

Px

Px

X.Y

none

Px

X.Y.Z

Pxyz

pxyz

上面的例子中,根记录器、X记录器、X.Y.Z记录器分别被分配了等级Proot、Px、Pxyz。X.Y记录器继承父亲X的等级Px。

记录器名

被指定等级

继承等级

root

Proot

Proot

X

Px

Px

X.Y

none

Px

X.Y.Z

none

Px

上面的例子中,根记录器、X记录器分别被分配了等级Proot、Px。X.Y和X.Y.Z记录器继承它们最近的被指定等级的记录器的等级。

记录请求通过调用记录器实例(使用log4net.ILog)的printing方法调用,printing方法包括Debug, Info, Warn, Error和Fatal。

通常,printing方法决定了记录请求的等级。例如,存在记录器实例log,那么log.Info(“…”)将发出等级为INFO的记录请求。

如果记录请求的等级高于或者等于记录器的等级将被允许。相反,请求将被禁止。没有指定等级的记录器将从层次结构中继承一个。这个规则归纳如下。

L>=K时,等级(指定或者继承)为K的记录器允许等级为L的记录请求。

这条规则是log4net的核心。它基于有序的规则,标准等级规则下,DEBUG<INFO<WARN<ERROR<FATAL。调用使用相同名称为参数的log4net.LogManager.GetLogger方法将返回完全相同的记录器的应用。例如

ILog x = LogManager.GetLogger("wombat");
ILog y = LogManager.GetLogger("wombat");

X和y指向完全相同的记录器。

因此,配置一个记录器并在代码中的某个地方不通过传递引用而获取相同的记录器成为可能。与生物学的父亲总是先于后代的基本原理不同,log4net记录器能够以任何顺序被配置。但是,即使父记录器晚于子记录器被配置,他也总是先被找到并链接到它的后代。

log4net环境通常在程序初始化时被配置。通常偏爱使用配置文件的方式,这个将在后面讨论(翻译调换了顺序,配置见前几节

log4net使通过组件命名记录器很简单。它可以通过在每个类中实例化记录器实现,记录器的名字为每个类的完整名称。这是一个定义记录器有用和直接的方法。随着输出的记录带有产生它的记录器的名字,这种命名规则简化了确认记录消息的来源方法。但是,这只是一种普遍的命名记录器的方法。Log4net不限制其他的命名方式。开发人员可以任意给记录器取名。

然而,按所在类命名记录器是目前已知的最好的方法。他给开发者明确指出了记录消息的来源。更重要的是这样可是使用程序的设计来设计记录器的层次结构。希望其中一些思想已进入到程序的设计中。

......

[阅读全文]

Log For log4net (三) — 配置语法

Saturday, April 28, 2007

上篇

注:这是log4net配置文档的最后一部份,描述了配置文档的语法

对于其中的renderer的意义不是很清楚

原文地址:log4net Manual - Configuration

log4net使用log4net.Config.XmlConfigurator配置读取器来解析XML,这一部分描述XmlConfigurator的语法

下面是一个有效的XML配置,根元素必须是<log4net>,这并不意味着这个元素不能嵌入到其他元素里面。详见配置文件节。

<log4net>
    <appender name="ConsoleAppender" type="log4net.Appender.ConsoleAppender" >
        <layout type="log4net.Layout.PatternLayout">
            <conversionPattern value="%date [%thread] %-5level %logger [%ndc] - %message%newline" />
        </layout>
    </appender>
    <root>
        <level value="INFO" />
        <appender-ref ref="ConsoleAppender" />
    </root>
</log4net>

<log4net>节支持下列属性

debug:可选属性,值为true或者false,默认为false。指定为true时允许log4net的内部调试
update:可选属性,值为Merge或者Overwrite,默认为Merge。指定为Overwrite时将在应用此配置之前重置库中的配置。
threshold:可选属性,值必须是已在库中已注册的等级名,默认为ALL。这个属性用来在整个库中限制需要记录的等级,不管记录到任何地方。
<log4net>支持下列子元素
appender:0或者多个,定义记录方式(appender)
logger:0或者多个,定义记录器(logger)的配置。
renderer:0或者多个,定义一个对象renderer(不知道怎么意思)
root:0个或者1个,定义根记录器
param:0或者多个,库特定参数。

Appenders

Appenders只可以定义为<log4net>元素的子元素,每个appender(记录方式)都必须被唯一的命名,appender的实现类型也必须制定。下面的例子定义了一个log4net.Appender.ConsoleAppender类型的记录器,即控制台记录方式

<appender name="ConsoleAppender" type="log4net.Appender.ConsoleAppender" >
    <layout type="log4net.Layout.PatternLayout">
        <conversionPattern value="%date [%thread] %-5level %logger [%ndc] - %message%newline" />
    </layout>
</appender>

<appender>节支持下列属性

name:必须属性,字符串表示的appender名称,名称必须在所有记录方式中唯一,它在记录器的<appender-ref>元素中被引用,用来指定记录方式。
type:必须属性,值为记录器类型名,如果记录器不在log4net组件集中,必须给出完整的组件集名称。
<appender>节支持下列子元素
appender-ref:0或者多个,允许记录方式引用其他记录方式,不被所有记录方式支持。
filter:0或者多个,定义记录方式的过滤器
layout:0个或者1个,定义记录方式的布局
param:0或者多个,记录方式特定参数。

例子参见Example Appender Configuration文档

Filters(过滤器)

filter只可以定义为<appender>元素的子元素

<filter>节支持下列属性
type:必须属性,值为过滤器的类型名称。如果过滤器不在log4net组件集中,必须给出完整的组件集名称。
<filter>节支持下列子元素
param:0或者多个,filter特定参数

过滤器组成一个串来规定能够通过的事件,每个过滤器能够指定接受和处理的事件、拒绝和停止处理的事件或者允许事件到下一个过滤器。如果事件达到filter串的尽头而且没有被拒绝,那么它将被隐式的被记录器接受。

<filter type="log4net.Filter.LevelRangeFilter">
    <levelMin value="INFO" />
    <levelMax value="FATAL" />
</filter>

这个过滤器将会拒绝等级低于INFO或者高于FATAL的事件,其他事件包括INFO和FATAL将会被记录。
如果我们只想允许消息中包含特定字符(如“database”)的事件被记录,那么可以使用下面的过滤器

<filter type="log4net.Filter.StringMatchFilter">
    <stringToMatch value="database" />
</filter>
<filter type="log4net.Filter.DenyAllFilter" />

上面的例子中,第一个过滤器将会检查时间消息中是否包含“database”,如果包含,过滤器将接受消息并停止过滤,事件被记录。如果不包含,事件被传递到下一个过滤器处理,如果没有下一个过滤器,时间会被隐式的记录。如果我们不想不符合过滤要求的事件被隐式记录,可以在最后加上log4net.Filter.DenyAllFilter,他将拒绝所有到达它的事件。

如果我们希望记录消息中包含“database”或者“ldap”的事件,可以使用下列过滤器

<filter type="log4net.Filter.StringMatchFilter">
    <stringToMatch value="database"/>
</filter>
<filter type="log4net.Filter.StringMatchFilter">
    <stringToMatch value="ldap"/>
</filter>
<filter type="log4net.Filter.DenyAllFilter" />
Layouts

Layout只可以定义为<appender>元素的子元素

<layout>节支持下列属性
type:必须属性,值为layout的类型名称。如果过滤器不在log4net组件集中,必须给出完整的组件集名称。
<layout>节支持下列子元素
param:0或者多个,filter特定参数

下面的例子表示了怎样使用log4net.Layout.PatternLayout来配置布局。

<layout type="log4net.Layout.PatternLayout">
    <conversionPattern value="%date [%thread] %-5level %logger [%ndc] - %message%newline" />
</layout>

Root Logger(根记录器)

root logger只可以定义为<log4net>元素的子元素,它在记录器层次结构的根部,其他记录器都继承自他。

根记录器的例子

<root>
    <level value="INFO" />
    <appender-ref ref="ConsoleAppender" />
</root>

<root>节不支持属性
<root>节支持下列子元素
appender-ref:0或者多个,允许记录器通过记录方式的名称引用记录方式
level:0个或者1个,定义记录器的记录等级,记录器只记录大于或等于这一等级的事件
param:0或者多个,记录器特定参数

Loggers(记录器)

logger只可以定义为<log4net>元素的子元素
一个记录器的例子

<logger name="LoggerName">
    <level value="DEBUG" />
    <appender-ref ref="ConsoleAppender" />
</logger>

<logger>节支持下列属性
name:必须属性,制定记录器的名称
additivity:可选属性,值为true或者false,默认为true。指定为false时将禁止记录器继承父记录器的记录方式。
<logger>节支持下列子元素
appender-ref:0或者多个,允许记录器通过记录方式的名称引用记录方式
level:0个或者1个,定义记录器的记录等级,记录器只记录大于或等于这一等级的事件
param:0或者多个,记录器特定参数

Renderers(映射?)不太懂

Renderer只可以定义为<log4net>元素的子元素
一个renderer例子

<renderer renderingClass="MyClass.MyRenderer" renderedClass="MyClass.MyFunkyObject" />

<renderer>节支持下列属性
renderingClass:必须属性,值为该renderer的类型名称,如果类型不在log4net组件集中,必须给出完整的组件集名称。它是为被render的类型负责的类型
renderedClass:必须属性,值为该renderer的目标类型名称,如果类型不在log4net组件集中。它是被render的的类型。

<renderer>节支持子元素

Parameters(参数)

parameter只可以定义为<log4net>元素的子元素
一个parameter例子

<param name="ConversionPattern" value="%date [%thread] %-5level %logger [%ndc] - %message%newline" />

<param>节支持下列属性
name:必须属性,值为传递到父对象中的parameter的名称
value:可选属性,该属性的值是一个被转换成参数(parameter)的值的字符串。
type:可选属性,该属性的值是一个用来创建和设置参数(parameter)的值的类型名称。如果类型不在log4net组件集中,必须给出完整的组件集名称。
value和type必须制定其中一个
<param>节支持下列子元素
param:0或者多个,参数特定参数
一个使用嵌套参数元素的例子

<param name="evaluator" type="log4net.spi.LevelEvaluator">
    <param name="Threshold" value="WARN"/>
<param>
扩展参数

配置参数直接映射对象的可写属性,可用的属性依赖于被配置对象的实际类型。log4net SDK文档包含了log4net组件集中所有部件的API参考。

紧凑参数语法

所有的参数可以使用参数名作为他们的元素名
例如

<param name="evaluator" type="log4net.spi.LevelEvaluator">
    <param name="Threshold" value="WARN"/>
<param>

可以写成

<evaluator type="log4net.spi.LevelEvaluator">
    <threshold value="WARN"/>
<evaluator>

......

[阅读全文]

Log For log4net (二) — 配置特性与配置文件

Friday, April 27, 2007

上篇

注:Attribute译为“特性”,Property译为“属性”

原文地址:log4net Manual - Configuration

配置特性

log4net除可以通过编程配置外还可以利用组件级别的特性进行配置

XmlConfiguratorAttribute

log4net.Config.XmlConfiguratorAttribute允许通过下列属性对XmlConfigurator进行配置。

configFile:指定对XmlConfigurator进使用的配置文件名,文件路径相对于程序的根文件夹(AppDomain.CurrentDomain.BaseDirectory)

ConfigFileExtension:指定配置文件的扩展名,组件集文件名加上指定的扩展名通常即配置文件名。例如从TestApp.exe中读取的组件制定ConfigFileExtension属性为log4net,则配置文件名为TestApp.exe.log4net。其效果与制定configFile属性为TestApp.exe.log4net相同。

配置文件的路径为程序根文件夹(AppDomain.CurrentDomain.BaseDirectory)。

这个属性不能与configFile属性一起使用。

Watch:如果这个属性设为true,程序将会监视配置文件并在文件变化时重新载入配置。

如果configFile和ConfigFileExtension属性均没有指定,log4net将会使用应用程序配置文件(例如TestApp.exe.config)

示例用法

   1: // Configure log4net using the .config file
   2: [assembly: log4net.Config.XmlConfigurator(Watch=true)]
   3: // This will cause log4net to look for a configuration file
   4: // called TestApp.exe.config in the application base
   5: // directory (i.e. the directory containing TestApp.exe)
   6: // The config file will be watched for changes.
   1: // Configure log4net using the .log4net file
   2: [assembly: log4net.Config.XmlConfigurator(ConfigFileExtension="log4net",Watch=true)]
   3: // This will cause log4net to look for a configuration file
   4: // called TestApp.exe.log4net in the application base
   5: // directory (i.e. the directory containing TestApp.exe)
   6: // The config file will be watched for changes.

特性在一个组件集中只能使用一次

使用特性可以清晰的指定应用程序从何处加载配置文件,但特性是完全被动的,将不会起任何作用,他们只是一些信息。因此如果你使用了配置特性必须调用log4net允让它读取特性,简单的调用LogManager.GetLogger便可以使程序集中的特性能够被读取和处理。因此程序启动后尽早调用logging方法是必要的,而且必须在任何外部的组件集在读取和调用之前。

配置文件

典型的log4net配置是通过文件,文件可以通过两种方式读取
1. 使用.NET System.Configuration API
2. 直接读取内容

.config 文件

System.Configuration API只在配置数据在应用程序的config文件中时才起作用,如MyApp.exe.config或者Web.config.因为System.Configuration API不支持载入config文件中configuration外的标记时使用log4net.Config.XmlConfigurator.ConfigureAndWatch方法,使用System.Configuration API最大的有点在于它相比直接读取需要较低的权限。

使用System.Configuration API进行配置的唯一方法是调用log4net.Config.XmlConfigurator.Configure() 方法或者 the log4net.Config.XmlConfigurator.Configure(ILoggerRepository) 方法.

为了能够把配置数据嵌入到.config文件中,节的名字必须在configSections元素中注册到.NET解释器,还必须指定log4net.Config.Log4NetConfigurationSectionHandler用来解释这一配置节。其中类型必须是完整的组件集名因为它被.NET config文件解释器读取而不是log4net。必须指定正确的log4net组件集名。下面的配置文件示范了正确的方式

   1: <?xml version="1.0" encoding="utf-8" ?>
   2: <configuration>
   3:     <configSections>
   4:         <section name="log4net" type="log4net.Config.Log4NetConfigurationSectionHandler, log4net" />
   5:     </configSections>
   6:     <log4net>
   7:         <appender name="ConsoleAppender" type="log4net.Appender.ConsoleAppender" >
   8:             <layout type="log4net.Layout.PatternLayout">
   9:                 <conversionPattern value="%date [%thread] %-5level %logger [%ndc] - %message%newline" />
  10:             </layout>
  11:         </appender>
  12:         <root>
  13:             <level value="INFO" />
  14:             <appender-ref ref="ConsoleAppender" />
  15:         </root>
  16:     </log4net>
  17: </configuration>

上面的理智中,指定了log4net组件集,他必须位于.NET运行时能够找到的地方,例如与程序相同的文件夹。如果log4net组件集位于GAC中,完整的组件集名还包括文化、版本和公钥。

当用.config文件来配置时,节名即XML元素名必须为log4net。

直接读取内容

XmlConfigurator能够直接读取XML文件并用它来配置log4net,包括.config文件如MyApp.exe.config或者Web.config。
不使用直接读取的唯一原因是应用程序的权限不能直接读取文件。因此必须使用NET configuration APIs

包含配置数据的文件可以通过任何接受System.IO.FileInfo对象的log4net.Config.XmlConfigurator方法指定。因为文件系统能够用于监测并通知文件的变化,所以ConfigureAndWatch 方法被用于监测配置文件并在未见改变时重载它配置log4net。

此外,log4net.Config.XmlConfiguratorAttribute可以用于指定配置文件。

配置从文件中的log4net节读取,文件中只能有一个log4net元素但是它可以位于XML中的任意等级,例如根元素

   1: <?xml version="1.0" encoding="utf-8" ?>
   2: <log4net>
   3:     <appender name="ConsoleAppender" type="log4net.Appender.ConsoleAppender" >
   4:         <layout type="log4net.Layout.PatternLayout">
   5:             <conversionPattern value="%date [%thread] %-5level %logger [%ndc] - %message%newline" />
   6:         </layout>
   7:     </appender>
   8:     <root>
   9:         <level value="INFO" />
  10:         <appender-ref ref="ConsoleAppender" />
  11:     </root>
  12: </log4net>
或者嵌入其他元素间。
   1: <?xml version="1.0" encoding="utf-8" ?>
   2: <configuration>
   3:     <configSections>
   4:         <section name="log4net" type="System.Configuration.IgnoreSectionHandler" />
   5:     </configSections>
   6:     <log4net>
   7:         <appender name="ConsoleAppender" type="log4net.Appender.ConsoleAppender" >
   8:             <layout type="log4net.Layout.PatternLayout">
   9:                 <conversionPattern value="%date [%thread] %-5level %logger [%ndc] - %message%newline" />
  10:             </layout>
  11:         </appender>
  12:         <root>
  13:             <level value="INFO" />
  14:             <appender-ref ref="ConsoleAppender" />
  15:         </root>
  16:     </log4net>
  17: </configuration>

上面的配置文件显示了怎么样把配置数据嵌入到.config文件中尽管文件能够被log4net直接读取。值得注意的是.NET config文件解释器如果发现没有在configSections元素注册的元素将会抛出一个异常。因此,上面的例子将log4net节注册,但是指定的处理类型为System.Configuration.IgnoreSectionHandler.这个内建的类表示已使用另外的方法来读取配置节。

......

[阅读全文]

Log For log4net (一) — 配置概述

最近在写程序时,觉得对程序进行log很有必要。比如一个程序试运行时,可以通过log了解程序的运行情况等。.NET 框架下有很多开源的log工具,log4net就是其中的代表。

以前学习英语的东西,都是只了解了一个大概。这次决定试试通过翻译英文文档来学习log4net,看看能不能有更多的收获。学习的过程就叫Log For log4net吧,好像有一点绕口:-)

第一部分选取了log4net手册的Configuration节。这一节比较长,打算分两次看完。

注:1. 代码中的英文注释将保留

         2. log译为“记录”;logger译为“记录器”

原文地址:log4net Manual - Configuration

配置

对应用程序进行记录需要一定量的规划和工作。观察说明大约4成的代码是用来完成记录工作,即使中等规模的程序也需要在代码中嵌入上千行的记录语句。因此有必要自动维护记录代码。

log4net环境是完全可程序化配置的,但是使用XML格式的配置文件对它进行配置将会更灵活方便。

让我们看看是怎样在例子程序MyApp中使用log4net来完成记录。
译注:这里的MyApp为控制台应用程序。

   1: using Com.Foo;
   2:  
   3: // Import log4net classes.
   4: using log4net;
   5: using log4net.Config;
   6:  
   7: public class MyApp 
   8: {
   9:     // Define a static logger variable so that it references the
  10:     // Logger instance named "MyApp".
  11:     private static readonly ILog log = LogManager.GetLogger(typeof(MyApp));
  12:  
  13:     static void Main(string[] args) 
  14:     {
  15:         // Set up a simple configuration that logs on the console.
  16:         BasicConfigurator.Configure();
  17:  
  18:         log.Info("Entering application.");
  19:         Bar bar = new Bar();
  20:         bar.DoIt();
  21:         log.Info("Exiting application.");
  22:     }
  23: }

MyApp首先声明导入log4net的相关类,然后定义了一个静态记录器变量,传入参数是该类的类名。

在MyApp中使用了下面的Bar类

   1: using log4net;
   2:  
   3: namespace Com.Foo
   4: {
   5:     public class Bar
   6:     {
   7:         private static readonly ILog log = LogManager.GetLogger(typeof(Bar));
   8:  
   9:         public void DoIt()
  10:         {
  11:             log.Debug("Did it again!");
  12:         }
  13:     }
  14: }

BasicConfigurator.Configure()方法创建了一个简单的log4net配置,这个方法增加了一个到控制台的根记录器,输入格式可以使用PatternLayout来设置,例如"%-4timestamp [%thread] %-5level %logger %ndc - %message%newline".

默认情况下,根记录器被分配到Level.DEBUG

上述代码的输出如下

   1: 0    [main] INFO  MyApp  - Entering application.
   2: 36   [main] DEBUG Com.Foo.Bar  - Did it again!
   3: 51   [main] INFO  MyApp  - Exiting application.

我们注意到在log4net中,子记录器只会连接到他们存在的上级。这里,名为Com.Foo.Bar的记录器直接连接到根记录器,因而将忽略未使用的Com或者Com.Foo的记录器。这种方法明显改进了性能和降低了log4net的内存使用。

MyApp类通过调用BasicConfigurator.Configure()方法来配置log4net,其他类只需要导入log4net命名空间,获取他们需要的记录器进行记录。

上面的例子总是输入相同的记录信息。当然,可以简单的修改MyApp使之在运行时刻控制记录。下面是一个轻微修改版本。

   1: using Com.Foo;
   2:  
   3: // Import log4net classes.
   4: using log4net;
   5: using log4net.Config;
   6:  
   7: public class MyApp 
   8: {
   9:     private static readonly ILog log = LogManager.GetLogger(typeof(MyApp));
  10:  
  11:     static void Main(string[] args) 
  12:     {
  13:         // BasicConfigurator replaced with XmlConfigurator.
  14:         XmlConfigurator.Configure(new System.IO.FileInfo(args[0]));
  15:  
  16:         log.Info("Entering application.");
  17:         Bar bar = new Bar();
  18:         bar.DoIt();
  19:         log.Info("Exiting application.");
  20:     }
  21: }

这个版本的MyApp使用XmlConfigurator来解析配置文件和建立记录环境,配置文件的路径通过命令行制定。
译注:不知道怎么赋值给命令行参数,我是直接传递log4net的配置文件的名称。

下面是一个简单的配置文件,它的效果与上面的基于BasicConfigurator的例子完全相同。

   1: <log4net>
   2:     <!-- A1 is set to be a ConsoleAppender -->
   3:     <appender name="A1" type="log4net.Appender.ConsoleAppender">
   4:  
   5:         <!-- A1 uses PatternLayout -->
   6:         <layout type="log4net.Layout.PatternLayout">
   7:             <conversionPattern value="%-4timestamp [%thread] %-5level %logger %ndc - %message%newline" />
   8:         </layout>
   9:     </appender>
  10:     
  11:     <!-- Set root logger level to DEBUG and its only appender to A1 -->
  12:     <root>
  13:         <level value="DEBUG" />
  14:         <appender-ref ref="A1" />
  15:     </root>
  16: </log4net>

如果我们不再想记录任何Com.Foo命名空间中类的记录输出,可以参照下面的配置文件

   1: <log4net>
   2:     <!-- A1 is set to be a ConsoleAppender -->
   3:     <appender name="A1" type="log4net.Appender.ConsoleAppender">
   4:  
   5:         <!-- A1 uses PatternLayout -->
   6:         <layout type="log4net.Layout.PatternLayout">
   7:             <!-- Print the date in ISO 8601 format -->
   8:             <conversionPattern value="%date [%thread] %-5level %logger %ndc - %message%newline" />
   9:         </layout>
  10:     </appender>
  11:     
  12:     <!-- Set root logger level to DEBUG and its only appender to A1 -->
  13:     <root>
  14:         <level value="DEBUG" />
  15:         <appender-ref ref="A1" />
  16:     </root>
  17:     
  18:     <!-- Print only messages of level WARN or above in the package Com.Foo -->
  19:     <logger name="Com.Foo">
  20:         <level value="WARN" />
  21:     </logger>
  22: </log4net>

输出将会是这样:

   1: 2000-09-07 14:07:41,508 [main] INFO  MyApp - Entering application.
   2: 2000-09-07 14:07:41,529 [main] INFO  MyApp - Exiting application.

因为记录器Com.Foo.Bar没有分配级别,它将继承配置文件中已分配为Warn级别的Com.Foo的级别,Bar.DoIt方法中记录语句的级别为DEBUG,低于WARN,因此DoIt方法的记录请求将被禁止。

下面是使用多记录方式的配置文件

   1: <log4net>
   2:     <appender name="Console" type="log4net.Appender.ConsoleAppender">
   3:         <layout type="log4net.Layout.PatternLayout">
   4:             <!-- Pattern to output the caller's file name and line number -->
   5:             <conversionPattern value="%5level [%thread] (%file:%line) - %message%newline" />
   6:         </layout>
   7:     </appender>
   8:     
   9:     <appender name="RollingFile" type="log4net.Appender.RollingFileAppender">
  10:         <file value="example.log" />
  11:         <appendToFile value="true" />
  12:         <maximumFileSize value="100KB" />
  13:         <maxSizeRollBackups value="2" />
  14:  
  15:         <layout type="log4net.Layout.PatternLayout">
  16:             <conversionPattern value="%level %thread %logger - %message%newline" />
  17:         </layout>
  18:     </appender>
  19:     
  20:     <root>
  21:         <level value="DEBUG" />
  22:         <appender-ref ref="Console" />
  23:         <appender-ref ref="RollingFile" />
  24:     </root>
  25: </log4net>

运行它控制台将会出现如下输出

   1: INFO [main] (MyApp.cs:16) - Entering application.
   2: EBUG [main] (Bar.cs:12) - Doing it again!
   3: INFO [main] (MyApp.cs:19) - Exiting application.

指的注意的是,因为根记录器有第二个记录方式,记录也会被输出到example.log文件,文件将会被回滚当他达到100KB,当回滚发生时,旧版本的example.log将会被重命名为example.log.1

注意,实现这些不同的记录行为并不需要重新编译程序。我们可以轻松的将记录输出到Email地址、Windows的事件记录器,或者远程的log4net服务器。

更多的使用XmlConfigurator实现多输出方式的例子 点击这里

......

[阅读全文]

Excel中的日期

Tuesday, April 24, 2007

今天使用koogra库读取Excel数据时才知到原来Excel中的日期都是用数值表示的,并且在文件中也是使用数值存储,从而在程序读出来的是这个表示日期的数值。起始日期是1900-1-1 0:00:00 ,它对应数值1。1900-1-2 0:00:00 则对应数值2,也就是说表示日期的数值单位是天。

如果要根据读取对应的日期,使用下述语句便可以简单的完成。记得要把表示日期的数值-1,

   1: Convert.ToDateTime("1900-01-01 0:0:0").AddDays(date - 1)

然后,由于Excel为了保持与 Lotus-123 的兼容性,保留了Lotus-123 把1900年算成闰年的Bug(实际上不是),所以1900-2-29也同样占用了一个数值60,因此1900-3-1 0:00:00 对应的数值变成了61。但是在.NET中是没有这个bug,因此用上述代码返回日期时,60则返回1900-3-1 0:00:00 。为了兼容这个问题,需要把表示日期的数值再减去1才能得到正确的结果。正确代码如下,数值60和61都表示日期1900-3-1 0:00:00

   1: if(date<=60){
   2:     return Convert.ToDateTime("1900-01-01 0:0:0").AddDays(date - 1);
   3: }
   4: else{
   5:     return Convert.ToDateTime("1900-01-01 0:0:0").AddDays(date - 2);
   6: }

......

[阅读全文]

使用Javascript 调用.NET WEB服务

Sunday, April 22, 2007

过去使用Javascript调用服务器端代码时,后台代码一直都是放在普通的ASPX页面的Page_Load方法内,一直觉得这种方式不是很好,每实现一个功能都要一个新的页面来实现,也许还有其他问题。如果能够使用WEB服务的话,就可以解决上述问题,如可以在一个Web服务页面内放置多个函数和代码,解决web服务调用验证等。

在网上搜索Javascript调用web服务的方法,找到一篇使用 Ajax 调用 SOAP Web 服务,但总是感觉好像太复杂了,要使用到它的Web Services JavaScript Library。另外一种我知道的方法就是使用ASP.NET Ajax,使用这个库调用web服务很简单并且实现的功能也很强。但是如果不使用这么重量的库能不能直接使用Javascript调用WEB服务呢?

一个偶然的机会,我发现使用ASP.NET编写的Web服务格式是这样的。加入名为Demo的asmx页面中有一个Web方法为foo()。那么调用它的时候,在地址栏显示的地址是Demo.asmx/foo。那么在Javascript能不能直接post(get)到服务器的这个页面呢?答案是可以,从而这样很简单的就实现了通过Javascript 调用WEB服务,也不需要其他库的支持。

下面用一个例子简单实现Javascript 调用WEB服务,假设WEB应用程序的Services/Demo.asmx中存在foo方法返回"Hello World"。

   1: [WebMethod]
   2: public string foo()
   3: {
   4:     return "Hello World";
   5: }

直接通过Services/Demo.asmx/foo访问,得到下面结果,是一个XML文件

   1: <?xml version="1.0" encoding="utf-8" ?> 
   2: <string xmlns="http://sacranto.blogspot.com/">Hello World</string> 

在Javascript中的调用如下:(Javascript中使用了jQuery库,这个库与是否调用WEB服务没有关系,下同)

   1: $(document).ready(function() {
   2:     $("#btnFoo").click(function(){
   3:         $.post("Services/Demo.asmx/foo",
   4:             function(res){
   5:                 alert($(res).text());
   6:             }
   7:         );
   8:     });
   9: });

这段代码用于显示返回xml中的字符,IE7和FF2的结果不同,个人认为FF2的好一些。不知道IE6和其他浏览器的结果怎么样?

12

那么能不能直接传递参数给WEB服务呢?在ASP.NET WEB服务中,答案仍然是可以(使用其他语言编写的WEB服务应该也可以吧)。借助于WebService.Context 获取当前请求的ASP.NET HttpContext,它封装了由 HTTP 服务器用来处理Web 请求的所有HTTP 特定的上下文。

修改请求的Javascript代码,多了传递参数的一行。

   1: $(document).ready(function() {
   2:     $("#btnFoo").click(function(){
   3:         $.post("Services/Demo.asmx/foo",
   4:             {name:'sacranto',age:'24'},
   5:             function(res){
   6:                 alert($(res).text());
   7:             }
   8:         );
   9:     });
  10: });

这样在web服务页面中,可以通过如下代码访问传递的参数

   1: string name = Context.Request.Form["name"];
   2: int age = Convert.ToInt32(Context.Request.Form["age"]);

解决了传递参数的问题,救下来就是返回值了。web服务直接返回的都是xml,我们也可以利用Respone返回其他格式,如text/plain。例如线面的fooText方法就返回了纯文本,记得把方法改成无返回值。

   1: [WebMethod]
   2: public void fooText()
   3: {
   4:     string name = Context.Request.Form["name"];
   5:     int age = Convert.ToInt32(Context.Request.Form["age"]);
   6:     Context.Response.Expires = -1;       
   7:     Context.Response.ContentType = "text/plain";
   8:     Context.Response.Write("Hello " + name + ", your age is " + age.ToString());
   9:     Context.Response.End();
  10: }

在Javascript中的调用与上面基本相同,只是回调类型是text,直接使用即可。这也意味着text可以直接是json字符串了。

   1: $("#btnFooText").click(function(){
   2:     $.post("Services/Demo.asmx/fooText",
   3:         {name:'sacranto',age:'24'},
   4:         function(res){
   5:             alert(res);
   6:         }
   7:     );
   8: });

看看结果

3

使用Javascript 调用.NET WEB服务,是不是很简单呢?

 

 

PS:据说这种方法使用 http://localhost/... 访问就正常, 但用域名会出错。 我试了用IP是没有问题的,用域名?我还没有域名呢 :-)

解决方法:
在Web.config里面加上就可以了.

   1: <webServices> 
   2:     <protocols> 
   3:         <add name="HttpPost" /> 
   4:         <add name="HttpGet" /> 
   5:     </protocols> 
   6: </webServices> 

......

[阅读全文]

Javascript 的命名空间、类、属性和方法

Saturday, April 21, 2007

今天在网上看了一些实现Javascript 的命名空间、类、属性和方法的文章,下面是我自己尝试的代码,由于对Javascript这种prototype型的语言的理解不是很深,理解可能有误,还好实现应该没有问题的:-)

一. 命名空间:分为两个部分。第一个部分是注册函数,另外一部分是声明命名空间

命名空间注册函数,参照yui的方式,注意这种方式下所有的命名空间的第一级都是sacranto。

   1: var sacranto = {};
   2: sacranto.namespace=function(ns){
   3:     if(!ns||!ns.length){
   4:         return null;
   5:     }
   6:     
   7:     var levels = ns.split(".");
   8:     var nsobj=sacranto;
   9:     
  10:     for(var i=(levels[0] == "sacranto")?1:0;i<levels.length;i++){
  11:         nsobj[levels[i]]=nsobj[levels[i]]||{};
  12:         nsobj = nsobj[levels[i]];
  13:     }
  14:     
  15:     return nsobj;
  16: };    

声明命名空间的语句,这样我们就注册了一个sacranto.demo命名空间。

   1: sacranto.namespace("demo");

二. 类:对于prototype类型的语言,函数其实就是类,声明一个函数其实就声明了一个类,并且他还是是类的静态构造函数。下面的语句声明了一个类,静态构造函数里面什么也没有做。

   1: sacranto.demo.aclass = function(){};

三. 类的属性和方法:这里就需要使用到prototype这个特性了

   1: sacranto.demo.aclass.prototype = {
   2:     //属性
   3:     version: "1.0",
   4:     //方法
   5:     bar: function(s){
   6:         return "hello " + s;
   7:     }
   8: };

四. 静态方法:仿佛在javascript里面应该没有静态方法这个概念吧。我的理解是这样的

   1: //我理解的静态方法,其实是另外一个类和它的静态构造函数。
   2: sacranto.demo.aclass.foo=function(s){
   3:     return "hello "+s;
   4: }

OK,完成,测试一下吧

   1: window.onload = function(){
   2:     var aobj=new sacranto.demo.aclass();
   3:     alert(aobj.bar("world"));                    //成功
   4:     //alert(sacranto.demo.aclass.bar("world"));    //失败
   5:     alert(sacranto.demo.aclass.foo("world"));    //成功
   6: };

 

延伸阅读:

Private Members in JavaScript 英文:讨论了类中的私有成员

prototype的一个优势也是缺点    中文:prototype实现类的属性和方法,以及在运行时刻会改变的特点。

 

PS:取消 使用IE打开包括JavaScript 的本地web页时收到安全警告 方法

1. 在 工具 菜单上, 单击 Internet选项
2. 单击 高级 选项卡
3. 在 设置 列表, 确定依次选中 安全--允许活动内容在本机上的文件中运行。此选项允许您运行脚本和 ActiveX 控件在 InternetExplorer 中
4. 在 文件 菜单上, 单击 关闭

......

[阅读全文]

配置iBatis.NET DataMapper

Wednesday, April 18, 2007

一. 在iBatis.Net的官方网站下载iBatis.NET DataMapper 1.6.1。这个是今年4月6号的最新版本,也是快一年以来的第一次更新。

下载的Release的版本解压缩以后看起来很杂乱,dll、xml和xsd等一大堆。

二. 为了能够在Visual中编写映射文件和配置文件时使用智能提示和校验,首先把provider.xsd、SqlMap.xsd和SqlMapConfig.xsd拷贝到[VS安装目录]\Xml\Schemas,这个路径是相对于VS 2005的,其他版本参看文档4.2.4节。

注:iBatis.NET的文档是需要单独下载的,不在Release包里面,下面提到的文档均是1.6.1版本的文档。

三. 使用iBatis.NET之前需要在VS的工程中添加对IBatisNet.DataMapper.dll引用,添加它的同时,它会自动将IBatisNet.Common.dll和Castle.DynamicProxy.dll添加到工程的bin目录下。

四. 编写SqlMap.config文件,并将其置于跟目录下,一般是Web.config(WEB应用程序)或者App.config(winform)所在的文件夹。下面是一个简单示例,修改自iBatis.NET文档。

   1: <?xml version="1.0" encoding="utf-8"?>
   2: <sqlMapConfig xmlns="http://ibatis.apache.org/dataMapper"
   3: xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" >
   4:   <properties resource="properties.config"/>
   5:   <settings>
   6:     <setting useStatementNamespaces="true"/>
   7:     <setting cacheModelsEnabled="true"/>
   8:     <setting validateSqlMap="false"/>
   9:   </settings>
  10:  
  11:   <database>
  12:     <provider name="sqlServer2.0"/>
  13:     <dataSource name="iBatisExt"  connectionString="user id=sa;password=sa;data source=10.14.91.244;database=northwind;"/>
  14:   </database>
  15:  
  16:   <sqlMaps>
  17:     <sqlMap resource="${root}Maps/Categories.xml"/>
  18:   </sqlMaps>
  19: </sqlMapConfig>

其中首先指定Properties文件的位置,此处的Properties.config与SqlMap.config位于相同位置,Properties文件一般用来定义“名称-值”对,例如下面使用到的${root}就是在Properties.config中定义的,这里使用的Properties文件如下,很简单:

   1: <?xml version="1.0" encoding="utf-8" ?>
   2: <settings>
   3:   <add key="root" value="./" />
   4: </settings>

Setting用于给定ibatis.net的一些属性,例如useStatementNamespaces是是否允许使用map文件中的命名空间,文档中的示例为false,注意把它改成true,因为下面的程序使用了map文件中的命名空间。

database用于配置映射的数据源,Provider我改成了sqlServer2.0。

五. 把定义数据源的providers.config拷贝到与SqlMap.config相同的目录下,打开providers.config,修改其中的

   1: <provider
   2:     name="sqlServer2.0"
   3:     enabled="false"

   1: <provider
   2:     name="sqlServer2.0"
   3:     enabled="true"

这里默认情况下,也就是从Release文件夹拷贝过来的providers.config是不打开sqlServer2.0,这个地方害我修改了半天。

六.  编写映射文件、实体类就完成了iBatis的配置。如果你不想自己编写映射文件、实体类,Go on

七.  我找到了一个iBatis.NET的CodeSmith模板,名字叫做IBatisNetGen,个人觉得很好用,它直接生成了映射文件、实体类、DAO接口和DAO层。注意在使用时制定他们的命名空间,免得生成后自己去修改。

八.  使用上述模板注意有一个地方需要修改,就是在把DAO模板里面需要把SqlMapperFmt属性改为你自己的Mapper类,如本文中使用iBatis.NET默认的Mapper.Instance()。

1

如果使用到了多数据库,要用不同的SqlMap文件,并编写不同的Mapper类,如这里完成了一个使用AnotherSqlMap.config文件的AnotherSqlMapper类(参考文档Example 4.4),注意红色的行与iBatis.net中文档4.4.1.1节有一点区别,4.4.1.1节的语句不能直接用于替换文档中的Example 4.4的对应语句,必须做红色标示的修改。奇怪一下文档为什么不前后一致呢?

   1: using IBatisNet.Common.Utilities;
   2: using IBatisNet.DataMapper;
   3: using IBatisNet.DataMapper.Configuration;
   4:  
   5: namespace Demo
   6: {
   7:     public class AnotherSqlMapper
   8:     {
   9:         private static volatile ISqlMapper _mapper = null;
  10:  
  11:         protected static void Configure(object obj)
  12:         {
  13:             _mapper = null;
  14:         }
  15:  
  16:         protected static void InitMapper()
  17:         {
  18:             ConfigureHandler handler = new ConfigureHandler(Configure);
  19:             DomSqlMapBuilder builder = new DomSqlMapBuilder();
  20:             _mapper = builder.ConfigureAndWatch("AnotherSqlMap.config",handler);
  21:         }
  22:  
  23:         public static ISqlMapper Instance()
  24:         {
  25:             if (_mapper == null)
  26:             {
  27:                 lock (typeof(SqlMapper))
  28:                 {
  29:                     if (_mapper == null)
  30:                     {
  31:                         InitMapper();
  32:                     }
  33:                 }
  34:             }
  35:             return _mapper;
  36:         }
  37:  
  38:         public static ISqlMapper Get()
  39:         {
  40:             return Instance();
  41:         }
  42:     }
  43: }

九 配置完成,以Northwind中的Categories表为例,实体类Categories。C#获取全部记录的调用语句。

   1: ICategoriesDao catDao=new CategoriesDao();
   2:         IList<Categories> cats = catDao.FindAll();
   3:         gvCategories.DataSource = cats;
   4:         gvCategories.DataBind();

......

[阅读全文]