Santé

为明天干杯!
posts - 47, comments - 320, trackbacks - 9, articles - 0
  博客园 :: 首页 :: 新随笔 :: 联系 :: 订阅 订阅 :: 管理

公告

.Net Eventlog:名字不好就不让你写Log!

Posted on 2006-01-06 17:27 smalldust 阅读(...) 评论(...) 编辑 收藏

EventLog,无论对于管理员还是对于程序员来说,虽然不算是一个噩梦,但是仍然是很麻烦的一个东西。先不说分析evt文件的繁琐的格式和直接操作注册表,就连用.Net傻瓜化的EventLog Class,都会发生意想不到的问题。

场景:
在Windows下,由于安全性的问题,很多时候我们的程序是不能以管理员权限运行的。但是众所周知,当程序以默认的普通用户权限运行的时候,是无法创建Event Log以及Source的。对此,一般的解决方法就是,在安装程序时使用管理员登录并进行安装(其中包括Event Log以及Source的创建和配置),安装完毕后再转到一般用户下运行。

我写的这个程序就是这样的:

①安装是在管理员权限下,用EventLog.CreateEventSource(SourceName, LogName);来创建一个Log,一个Source,并将其绑定。

②然后再普通用户的权限下运行(未做过任何权限修改的默认用户),使用下面的语句写入Log:
EventLog.WriteEntry(SourceName, EventMessage);

就是这样简单的一个结构,没想到会有问题。

我在测试的时候使用的Source名称为“MySource”,建立的Log为“MyLog”,一切正常;
但是在真正拿到客户那里运行时,由于客户的要求,Log名称定为“ToolLog”,结果在第二步写入Log时,竟然抛出了异常:

System.Security.SecurityException: Requested registry access is not allowed.
at Microsoft.Win32.RegistryKey.OpenSubKey(String name, Boolean writable)
at System.Diagnostics.EventLog.FindSourceRegistration(String source, String machineName, Boolean readOnly)
at System.Diagnostics.EventLog.SourceExists(String source, String machineName)
at System.Diagnostics.EventLog.WriteEntry(String message, EventLogEntryType type, Int32 eventID, Int16 category, Byte[] rawData)
at System.Diagnostics.EventLog.WriteEntry(String source, String message, EventLogEntryType type, Int32 eventID, Int16 category, Byte[] rawData)
at System.Diagnostics.EventLog.WriteEntry(String source, String message, EventLogEntryType type, Int32 eventID, Int16 category)
at System.Diagnostics.EventLog.WriteEntry(String source, String message, EventLogEntryType type, Int32 eventID)
at MyProgram.MyForm.button1_Click(Object sender, EventArgs e)
……以下省略……


从调用链可以明显看到,当调用WriteEntry写入Log的时候,.Net首先调用了SourceExist来判断Source是否存在。
是不是判断Source是否存在需要更多的权限呢?但是,在使用MyLog作为Log名的时候没有出现异常,为什么Log名改为ToolLog就出问题了呢?
进一步分析即可明白原因了。SourceExist调用了FindSourceRegistration,而FindSourceRegistration当中的核心代码是这样写的(为方便理解,与实际情况有所出入):

    // 打开EventLog主键
    RegistryKey kEvent = Registry.LocalMachine.OpenSubKey(@"SYSTEM\CurrentControlSet\Services\EventLog"false);

    
//对于EventLog下的每个Log进行遍历
    foreach (string k in kEvent.GetSubKeyNames())
    { 
        RegistryKey kLog = kEvent.OpenSubKey(k, false);

        
if (kLog != null)
        {
            
// 查看该Log下是否有所要找的Source
            RegistryKey kSource = kLog.OpenSubKey(source, false);
            
            
// 如果找到了就返回当前的Log
            if (kSource != null)
            {
                kEvent.Close();
                kSource.Close();
                
return kLog;
            }
            kLog.Close();
        }
    }


这段代码本来是没有问题的;但是偏偏在所有的Event Log当中,Security Log对于普通用户来说,不仅不能写,读都是不能读的!所以在上面黄色标记的语句如果遍历到“Security”Log,程序试图打开“Security”子键,由于没有权限就会抛出异常。
而所有的Event Log又是按照字母顺序排列的,因此如果Log名称在“Security”之前的话(例如上文的“MyLog”,M < S),FindSourceRegistration方法就会找到你的Source,然后return;但是如果不幸你的Log名称排在“Security”之后(例如“ToolLog”,T > S),那么在遍历到Security这个Log的时候还没有找到你的Source,那么就会出错。因此才有了根据Log名称不同,有时出错有时正常的奇怪现象。

解决方法:
这个问题,其实不能算.Net的Bug;但是事实上的确是一个重大的隐患,因为它有可能导致本来具有权限的用户无法读写自己的Log。
目前我的程序不得不使用了一个“Security”之前的Log名称来保证正常运行,但是说实话还是比较恶心,因为还要提醒用户,不要把Log命名为“Security”之后的字串。
个人觉得,WriteEntry方法里面根本不用附带自动创建Source和Log的功能(如果不要这个功能,就不需要在WriteEntry时调用SourceExist了),因为WriteEntry内部调用的是WINAPI中的RegisterEventSource函数获取Handle,然后用ReportEvent写入Log的,RegisterEventSource函数如果没有找到指定的Source,会自动用Application来代替,并不会引起错误。

此外,EventLog除了这个问题之外,还有若干并非Bug,但是非常难用的地方,以后再找机会一起发牢骚吧。


由于没有在.Net 2.0下做测试,不知道2.0下是否已经修正这个问题,由于比较匆忙也不知道网上是否早已有这个问题的探讨,还请知道的朋友告诉我一下。
如果有比较完美的解决办法,也请告知。