原先打算再写点EXT.NET方面的,由于现在在玩Silverlight和Android,所以不打算写下去了。而且最近很忙,所以发帖也不会很及时。
好了,转入正题了:最近客户有个需求,要求写个小程序监控生产线上的苹果一体机的日志文件并上传到服务器。开始想使用Perl或者Python,但是时间不够(因为对这两门语言还不太熟悉),于是想试试MONO。MONO虽然没想象中好用,但是还算勉强能用。
虽然MonoDevelop还可以,但是还是比较喜欢使用VS开发。于是在VS里面先编码。值得注意的是:
- 如果是开发Winform程序,界面用GTK重画吧。
- 如果是在VS里面开发,最好将所有的编码和注释都用英文。否则换其他系统时,可能会出现乱码。
- 编写时需注意MONO是否支持,否则就是白写。
接下来,开始编码。
1.读取配置文件
习惯用XML,发现不支持System.Xml.Linq,亦不支持System.Xml,读取ini文件也麻烦,于是干脆读取文本文件好了。示例配置如下:
MonitoringDirectoryType:['Immediate','Cycle'] Path:['/Users/PRODUCTION/Public/FinalTest/LogsFolder','/Users/PRODUCTION/Public/FinalTest/LogsFolder'] TargetPath:['/Volumes/mes_data/FINALTEST/n81a/{Y}/{M}/{D}/FIN012','/Volumes/mes_data/FINALTEST/n81a/{Y}/{M}/{D}/FIN012'] IncludeSubdirectories:['FALSE','false'] Filter:['*.TXT','*.CSV'] BackupPath:['/Users/PRODUCTION/BACKUP/{Y}/{M}/{D}','/Users/PRODUCTION/BACKUP/{Y}/{M}/{D}'] BackupExpired:['2','2'] CycleMinutes:['','1440']
相关解析代码如下:
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.IO; using MonitoringApp.Custom; using System.Text.RegularExpressions; using Gtk; namespace MonitoringApp.Code.App { public class Configuration { private static bool isLoadFirst = false; private static List<MyConfig> configLst; public static List<MyConfig> ConfigLst { get { if (configLst == null) { try { if (!isLoadFirst) return LoadFromInitParams(); else return null; } catch (Exception ex) { LogManager.WriteErrorLog(ex); return null; } } return configLst; } set { configLst = value; } } /// <summary> /// Load Configuration file /// </summary> /// <returns></returns> private static List<MyConfig> LoadFromInitParams() { isLoadFirst = true; string _configPath = Path.Combine(Directory.GetCurrentDirectory(), "Configuration.txt"); if (File.Exists(_configPath)) { if (ConfigLst == null) ConfigLst = new List<MyConfig>(); else return ConfigLst; using (StreamReader sr = new StreamReader(_configPath)) { int lineIndex = 1; while (sr.Peek() > 0) { string str = sr.ReadLine().Trim(); if (str.Length == 0) continue; if (!str.Contains("[") || !str.Contains("]")) { LogManager.WriteErrorLog(string.Format("Config Error:'[' OR ']' Not Exist.At Line {0}.", lineIndex)); continue; } if (!str.Contains(":")) { LogManager.WriteErrorLog(string.Format("Config Error:':' Not Exist.At Line {0}.", lineIndex)); continue; } string[] names = str.Split(':'); str = str.Substring(str.IndexOf(':') + 1).Trim(']').Trim('[').Trim('\''); string[] strConfigs = str.Split(','); SetConfig(names, ConfigLst, strConfigs, lineIndex == 1); lineIndex++; } } return ConfigLst; } else { LogManager.WriteErrorLog(string.Format("can't find config file.Path:{0}.",_configPath)); return null; } } /// <summary> /// Set Config /// </summary> /// <param name="name"></param> /// <param name="lst"></param> /// <param name="strConfigs"></param> private static void SetConfig(string[] name, List<MyConfig> lst, string[] strConfigs, bool isLineOne) { try { var mcConfig = (MonitoringConfigurations)Enum.Parse(typeof(MonitoringConfigurations), name[0], true); //Set Values for (int i = 0; i < strConfigs.Length; i++) { string value = strConfigs[i].Trim('\''); switch (mcConfig) { case MonitoringConfigurations.MonitoringDirectoryType: { #region MonitoringDirectoryType Defalut:immediate switch (value.ToLower()) { case "immediate": { if (isLineOne) { MyConfig myConfig = new MyConfig(); myConfig.MDType = MonitoringDirectoryType.Immediate; lst.Add(myConfig); } else { lst[i].MDType = MonitoringDirectoryType.Immediate; } break; } case "cycle": { if (isLineOne) { MyConfig myConfig = new MyConfig(); myConfig.MDType = MonitoringDirectoryType.Cycle; lst.Add(myConfig); } else { lst[i].MDType = MonitoringDirectoryType.Cycle; } break; } default: { if (isLineOne) { MyConfig myConfig = new MyConfig(); myConfig.MDType = MonitoringDirectoryType.Immediate; lst.Add(myConfig); } else { lst[i].MDType = MonitoringDirectoryType.Immediate; } break; } } #endregion break; } case MonitoringConfigurations.Path: #region Path if (isLineOne) { MyConfig myConfig = new MyConfig(); myConfig.Path = value; lst.Add(myConfig); } else { lst[i].Path = value; } #endregion break; case MonitoringConfigurations.IncludeSubdirectories: #region IncludeSubdirectories if (isLineOne) { MyConfig myConfig = new MyConfig(); myConfig.IncludeSubdirectories = Convert.ToBoolean(value); lst.Add(myConfig); } else { lst[i].IncludeSubdirectories = Convert.ToBoolean(value); } #endregion break; case MonitoringConfigurations.Filter: #region Filter if (isLineOne) { MyConfig myConfig = new MyConfig(); myConfig.Filter = value; lst.Add(myConfig); } else { lst[i].Filter = value; } break; #endregion case MonitoringConfigurations.BackupPath: #region BackupPath if (isLineOne) { MyConfig myConfig = new MyConfig(); myConfig.BackupPath = value; lst.Add(myConfig); } else { lst[i].BackupPath = value; } #endregion break; case MonitoringConfigurations.TargetPath: #region TargetPath if (isLineOne) { MyConfig myConfig = new MyConfig(); myConfig.TargetPath = value; lst.Add(myConfig); } else { lst[i].TargetPath = value; } #endregion break; case MonitoringConfigurations.BackupExpired: #region BackupExpired Unit:days Default:30 if (isLineOne) { MyConfig myConfig = new MyConfig(); myConfig.BackupExpired = Convert.ToInt32(value.Length == 0 ? "30" : value); lst.Add(myConfig); } else { lst[i].BackupExpired = Convert.ToInt32(value.Length == 0 ? "30" : value); } #endregion break; case MonitoringConfigurations.CycleMinutes: #region CycleMinutes Unit:Minute Default:60 if (isLineOne) { MyConfig myConfig = new MyConfig(); myConfig.CycleMinutes = Convert.ToInt32(value.Length == 0 ? "60" : value); lst.Add(myConfig); } else { lst[i].CycleMinutes = Convert.ToInt32(value.Length == 0 ? "60" : value); } #endregion break; default: break; } } } catch (Exception ex) { LogManager.WriteErrorLog(ex); } } } }
值得注意的是:
- 这里是按行读取的。从读取配置来看,可以看出,是支持多个配置项的。
- 如果出错,会记录日志。
- 和客户讲解如何配置是一件麻烦的事情。相关配置解释如下:
MonitoringDirectoryType:目录类型,仅支持两个值(Immediate、Cycle)。Immediate表示即时监控(默认值),Cycle表示周期监控。 Path:监控目录路径。必须是存在的路径。 TargetPath:目标目录路径。可以是远程目录路径。不能使用SMB路径,而应该是用如“/Volumes/mes_data/n81a”的类型。 IncludeSubdirectories:是否涵盖子目录。 Filter:过滤字符串。如“*”表示监控所有文件,“*.txt”表示监控所有的文本文件。 BackupPath:备份路径。 BackupExpired:备份过期时间。单位是天。必须为整数。默认值30天。 CycleMinutes:周期循环时间。单位是分。 必须为整数。默认值60分钟。
然后针对配置写个说明文档,这样客户就会配置了。
2)记录日志
程序运行起来总不可避免的会遇到各种问题,记录日志就不可避免了。相关代码如下:
using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Text; using Gtk; namespace MonitoringApp.Code.App { public class LogManager { public static object obj = new object(); static LogManager() { } #region Fields and Properties private static string _logPath = string.Empty; public static string LogPath { get { lock (obj) { if (_logPath == string.Empty) { var _logStr = string.Format("{0}/log/{1}/{2}", Path.Combine(Directory.GetCurrentDirectory(), "Data"), DateTime.Now.ToString("yyyy-MM"), DateTime.Now.Day); if (!System.IO.Directory.Exists(_logStr)) System.IO.Directory.CreateDirectory(_logStr); return string.Format("{0}/", _logStr); } return _logPath; } } set { _logPath = value; } } private static string _logFielPrefix = string.Empty; public static string LogFielPrefix { get { return _logFielPrefix; } set { _logFielPrefix = value; } } #endregion #region Log public static void WriteLog(string logFile, string msg) { try { lock (obj) { var _sb = new System.Text.StringBuilder(); _sb.Append(LogPath).Append(LogFielPrefix).Append(logFile).Append(".Log"); using (var _sw = System.IO.File.AppendText(_sb.ToString())) { _sw.WriteLine("==============================={0}===============================", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")); _sw.WriteLine(msg); _sw.Flush(); _sw.Close(); Console.WriteLine(msg); } } } catch { } } public static void WriteLog(LogFile logFile, string msg) { WriteLog(logFile.ToString(), msg); } public static void WriteTraceLog(string msg) { Console.WriteLine(msg); } public static void WriteErrorLog(string msg) { WriteLog(LogFile.Error, msg); } public static void WriteErrorLog(Exception ex) { var _sbError = new StringBuilder(); _sbError.Append("[Message]").Append(ex.Message); _sbError.Append("\r\n[TargetSite]").Append(ex.TargetSite); _sbError.Append("\r\n[StackTrace]").Append(ex.StackTrace); _sbError.Append("\r\n[Source]").Append(ex.Source); foreach (var _item in ex.Data.Keys) { _sbError.Append("\r\n[").Append(_item).Append("]").Append(ex.Data[_item]); } if (ex.InnerException != null && ex.InnerException.Message != ex.Message) { _sbError.AppendLine("____________________________________________________________________________________"); _sbError.Append("[Message]").Append(ex.InnerException.Message); _sbError.Append("\r\n[TargetSite]").Append(ex.InnerException.TargetSite); _sbError.Append("\r\n[StackTrace]").Append(ex.InnerException.StackTrace); _sbError.Append("\r\n[Source]").Append(ex.InnerException.Source); _sbError.AppendLine("____________________________________________________________________________________"); } WriteLog(LogFile.Error, _sbError.ToString()); } public static void WriteWarningLog(string msg) { WriteLog(LogFile.Warning, msg); } #endregion } public enum LogFile { Trace, Warning, Error, SQL, System } }
值得注意的是“Directory.GetCurrentDirectory()”可以获取到当前用户的个人目录,至于获取到程序当前目录路径,Winform的那一套不管用(如果哪位朋友找到了适合的方法,请留言)。苹果系统和Window系统差别太大,从没玩过苹果系统,刚开始玩还挺不习惯的。
接下来,讲述如何监控目录以及定时备份,最后讲述如何发布部署。发布部署才是最重要的一环。不上生产线,就不知道会有什么问题。当初以为这个小程序,一天就能搞定,结果中秋3天都废了。还好万幸搞定。时间关系,就此打住。