├── .gitignore ├── Editor.meta ├── Editor ├── EditorHelper.cs ├── EditorHelper.cs.meta ├── MS.Log4Unity.Editor.asmdef └── MS.Log4Unity.Editor.asmdef.meta ├── README.md ├── README.md.meta ├── Runtime.meta ├── Runtime ├── AppTrack.cs ├── AppTrack.cs.meta ├── Appenders.meta ├── Appenders │ ├── Appender.cs │ ├── Appender.cs.meta │ ├── AppendersManager.cs │ ├── AppendersManager.cs.meta │ ├── CatagoryFilterAppender.cs │ ├── CatagoryFilterAppender.cs.meta │ ├── FileAppender.cs │ ├── FileAppender.cs.meta │ ├── UnityLogAppender.cs │ └── UnityLogAppender.cs.meta ├── ConditionalLogger.cs ├── ConditionalLogger.cs.meta ├── Configurator.cs ├── Configurator.cs.meta ├── LogFactory.cs ├── LogFactory.cs.meta ├── Logger.cs ├── Logger.cs.meta ├── MS.Log4Unity.asmdef ├── MS.Log4Unity.asmdef.meta ├── PatternHelper.cs └── PatternHelper.cs.meta ├── package.json └── package.json.meta /.gitignore: -------------------------------------------------------------------------------- 1 | **/.DS_Store 2 | -------------------------------------------------------------------------------- /Editor.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: c25f9a57c017342e7a8d256f36525318 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /Editor/EditorHelper.cs: -------------------------------------------------------------------------------- 1 | using System.Collections; 2 | using System.Collections.Generic; 3 | using UnityEngine; 4 | using LitJson; 5 | using System.IO; 6 | namespace MS.Log4Unity.Editor{ 7 | using Configurations; 8 | internal class EditorHelper 9 | { 10 | public const string DEFAULT_CONFIG_FILE_PATH = "Assets/Resources/log4unity.json"; 11 | 12 | [UnityEditor.MenuItem("Edit/Log4Unity/CreateConfigFile")] 13 | public static void CreateDefaultConfigFileAtResources(){ 14 | 15 | if(File.Exists(DEFAULT_CONFIG_FILE_PATH)){ 16 | Debug.LogWarning("already exists file at:" + DEFAULT_CONFIG_FILE_PATH); 17 | return; 18 | } 19 | var basicLayout = new JsonData(); 20 | basicLayout.SetJsonType(JsonType.Object); 21 | basicLayout["type"] = "basic"; 22 | 23 | var basicTimeLayout = new JsonData(); 24 | basicTimeLayout.SetJsonType(JsonType.Object); 25 | basicTimeLayout["type"] = "basic-time"; 26 | 27 | var consoleConfigs = new JsonData(); 28 | consoleConfigs.SetJsonType(JsonType.Object); 29 | consoleConfigs["layout"] = basicLayout; 30 | 31 | 32 | var fileConfigs = new JsonData(); 33 | fileConfigs.SetJsonType(JsonType.Object); 34 | fileConfigs["rollType"] = "Session"; 35 | fileConfigs["env"] = Env.EditorPlayer.ToString(); 36 | fileConfigs["layout"]= basicTimeLayout; 37 | 38 | var c = new Configuration(){ 39 | appenders = new Dictionary(){ 40 | {"console",new Appender(){ 41 | type = "UnityLogAppender", 42 | configs = consoleConfigs, 43 | }}, 44 | { 45 | "file",new Appender(){ 46 | type = "FileAppender", 47 | configs = fileConfigs, 48 | } 49 | } 50 | }, 51 | catagories = new List(){ 52 | new Catagory(){ 53 | name = "default", 54 | appenders = new string[]{"console","file"}, 55 | level = "all", 56 | }, 57 | }, 58 | }; 59 | var writter = new JsonWriter(); 60 | writter.PrettyPrint = true; 61 | JsonMapper.ToJson(c,writter); 62 | var dir = Path.GetDirectoryName(DEFAULT_CONFIG_FILE_PATH); 63 | if(!Directory.Exists(dir)){ 64 | Directory.CreateDirectory(dir); 65 | } 66 | File.WriteAllText(DEFAULT_CONFIG_FILE_PATH,writter.ToString()); 67 | UnityEditor.AssetDatabase.ImportAsset(DEFAULT_CONFIG_FILE_PATH); 68 | } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /Editor/EditorHelper.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 6a1da1a48e9ee49f99e68ea9f1f53765 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Editor/MS.Log4Unity.Editor.asmdef: -------------------------------------------------------------------------------- 1 | { 2 | "name": "MS.Log4Unity.Editor", 3 | "references": [ 4 | "MS.Log4Unity" 5 | ], 6 | "optionalUnityReferences": [], 7 | "includePlatforms": [ 8 | "Editor" 9 | ], 10 | "excludePlatforms": [], 11 | "allowUnsafeCode": false, 12 | "overrideReferences": false, 13 | "precompiledReferences": [], 14 | "autoReferenced": true, 15 | "defineConstraints": [] 16 | } -------------------------------------------------------------------------------- /Editor/MS.Log4Unity.Editor.asmdef.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 1ca2626c00d33443191cc88124c605ac 3 | AssemblyDefinitionImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Log4Unity 2 | 3 | 类似于log4js的日志系统实现。 4 | 5 | # Dependencies 6 | 7 | - You must have `LitJson.dll` in your Project. [Here is a LitJson.dll wrapper for Unity Package](https://github.com/wlgys8/LitJsonUPM) 8 | 9 | - [FileRoller](https://github.com/wlgys8/FileRoller) 10 | 11 | # Install 12 | 13 | add follow to Package/manifest.json 14 | ```json 15 | "com.ms.log4unity":"https://github.com/wlgys8/Log4Unity.git" 16 | 17 | ``` 18 | 19 | or install by openupm 20 | 21 | ```sh 22 | openupm install com.ms.log4unity 23 | ``` 24 | 25 | # Usage 26 | 27 | 28 | ## Quick Start 29 | 30 | ```csharp 31 | 32 | private static MS.Log4Unity.ULogger logger = LogFactory.GetLogger(); 33 | 34 | void Start(){ 35 | logger.Debug("hello debug"); 36 | logger.Info("hello info"); 37 | logger.Warn("hello warn"); 38 | logger.Error("hello error"); 39 | logger.Fatal("hello fatal"); 40 | } 41 | 42 | ``` 43 | 44 | 45 | ## 配置文件 46 | 47 | ### 1. 查找方式 48 | 49 | - 默认情况下,使用`Resources/log4unity.json`作为配置文件 50 | - 可以通过`LogFactory.Configurate`来指定自己的配置文件 51 | - 如果查找不到配置文件,那么不会输出任何日志信息 52 | 53 | ### 2. 配置格式 54 | 55 | ```json 56 | { 57 | "appenders":{ 58 | "console":{ 59 | "type":"UnityLogAppender", 60 | } 61 | }, 62 | 63 | "catagories":[ 64 | { 65 | "name":"default", 66 | "appenders":["console"], 67 | "level":"all" 68 | } 69 | ] 70 | } 71 | 72 | ``` 73 | 74 | 以上是一个简单的配置文件. 其中`catagories`可以对logger进行分类,`appenders`定义不同类型的输出。 75 | 76 | `LogFactory.GetLogger()` 默认获取的`catagory`是 `default`。 77 | 也可使用 `LogFactory.GetLogger(catagory)`指定。 78 | 79 | ### 3. Catagory配置 80 | 81 | 目前支持如下字段: 82 | 83 | - matchType - string, 类型名字匹配方式,支持 (exact|regex) 84 | - exact 精确匹配 85 | - regex 正则匹配 86 | - name - string, 类型名字 87 | 88 | - appenders - string[] , 定义日志输出的目标 89 | 90 | - level - string, 定义日志输出级别,支持`all,debug,info,warn,error,fatal,off`,默认为`all` 91 | 92 | ### 4. Appenders 93 | 94 | Appender通常由`type`与`configs`组成. 系统通过`type`来索引查找相应的Appender. 并使用`configs`对其进行配置。 内置的Appenders如下: 95 | 96 | 97 | - UnityLogAppender - 输出到unity的debug系统 98 | - type : UnityLogAppender 99 | - configs 100 | - layout - Layout 日志格式化配置 101 | - env - Env 执行环境 102 | 103 | - FileAppender 104 | - type : FileAppender 105 | - configs 106 | - layout - Layout 日志格式化配置 107 | - env - Env 执行环境 108 | - rollType - 支持`Size`,`Session`,`Date` 109 | - fileName - string 文件输出路径 110 | - keepExt - 备份时,是否保持后缀 111 | - maxBackups - number 最多保存文件数量,默认为3 112 | - maxFileSize - number 单个日志文件大小,单位为byte。默认为1MB 113 | - flushInterval - number 日志按一定周期从内存持久化到硬盘。默认为1000ms 114 | 115 | - CatagoryFilterAppender 116 | - type: CatagoryFilterAppender 117 | - configs: 118 | - env - Env 执行环境 119 | - catagory - string 过滤的catagory正则匹配 120 | - appenders - string[] 重定向appender 121 | - 122 | 123 | - 自定义Appender 124 | 125 | 126 | 其中Env为枚举字符串,定义如下: 127 | 128 | - `EditorPlayer` 只在编辑器中有效 129 | - `BuiltPlayer` 只在打包后有效 130 | - `All` 均生效 131 | 132 | 133 | ### 5. 日志格式化 Layout 134 | 135 | 定义如下: 136 | ```json 137 | { 138 | "type":"layout-type", 139 | } 140 | ``` 141 | 142 | type支持以下类型 143 | 144 | - basic 145 | - basic-time 146 | - coloured 147 | - coloured-time 148 | - pattern 149 | 150 | type为pattern时,额外支持以下字段: 151 | 152 | ```json 153 | { 154 | "type":"pattern", 155 | "pattern":"" 156 | } 157 | ``` 158 | `pattern`定义了日志的输出格式。 159 | 160 | 支持以下字段: 161 | 162 | - `%r` time in toLocaleTimeString format 163 | - `%p` log level 164 | - `%c` log category 165 | - `%m` log data 166 | - `%d` date, formatted - default is ISO8601, format options are: ISO8601, ISO8601_WITH_TZ_OFFSET, ABSOLUTE, DATE, or any string - compatible with the date-format library. e.g. %d{DATE}, %d{yyyy/MM/dd-hh.mm.ss} 167 | - `%n` newline 168 | - `%[` start a coloured block (colour will be taken from the log level, similar to colouredLayout) 169 | - `%]` end a coloured block 170 | 171 | 172 | # Appender Type 类型映射规则 173 | 174 | 日志系统需要将配置的Appender Type映射为代码中对应的类型。内部会按照如下规则按顺序进行查找,直到获得对应的类型。 175 | 176 | - 查找通过`AppenderManager.RegisterAppenderType`注册的Appender类型 177 | - 根据配置表中的`appenderTypesRegister`进行映射 178 | - 直接使用`type`字段 179 | 180 | 181 | # 编译期优化 182 | 183 | ### 1. LogLevel和Filter未解决的问题 184 | 185 | LogLevel和Filter是在运行期对日志进行过滤的,虽然避免了日志流向Appenders引起的开销,但是却无法避免字符串构造的开销。例如以下例子: 186 | 187 | ```csharp 188 | 189 | void Update(){ 190 | logger.Info("Update"); 191 | } 192 | 193 | ``` 194 | 195 | 我们在Update中输出日志,即便通过LogLevel关掉了日志,但是每帧仍然有构造字符串`"Update"`的开销。还会引起GC卡顿。 196 | 197 | ### 2. 在编译期进行过滤 198 | 199 | 200 | 对于高频调用的调试日志接口,我们期望更进一步的优化。最好是对它们`Compile Out`。 这时候我们可以使用 `ConditionalLogger` 201 | 202 | ```csharp 203 | private static MS.Log4Unity.ConditionalLogger logger = LogFactory.GetConditionalLogger(); 204 | 205 | void Start(){ 206 | logger.EditorDebug("debug in editor only"); 207 | logger.Debug("debug"); 208 | logger.Info("info"); 209 | logger.Warn("warn"); 210 | logger.Error("error"); 211 | logger.Fatal("fatal"); 212 | } 213 | 214 | ``` 215 | 216 | 以上的代码,默认情况下,在编辑器里,只会输出 217 | ``` 218 | debug in editor only 219 | error 220 | fatal 221 | 222 | ``` 223 | 打包后,则只会输出 224 | 225 | ``` 226 | error 227 | fatal 228 | 229 | ``` 230 | 231 | 如果我们查看打包后最终编译的代码,会发现结果如下: 232 | 233 | ```csharp 234 | private static MS.Log4Unity.ConditionalLogger logger = LogFactory.GetLogger().Conditional(); 235 | 236 | void Start(){ 237 | logger.Error("error"); 238 | logger.Fatal("fatal"); 239 | } 240 | 241 | ``` 242 | 243 | 即: 244 | - 针对`logger.Editor{XXX}`打头的方法调用,仅会在编辑器环境被编译。 245 | 246 | - `logger.Debug`仅在`#if LOG4UNITY_DEBUG`为true时,被编译。 247 | - `logger.Info`仅在`#if LOG4UNITY_INFO`为true时,被编译。 248 | - `logger.Warn`仅在`#if LOG4UNITY_WARN`为true时,被编译。 249 | 250 | 这里内部是利用了 `System.Diagnostics.Conditional`标签特性. 251 | 252 | 253 | 关于如何在Unity中加入宏定义,请搜索 `Platform custom #defines` 254 | -------------------------------------------------------------------------------- /README.md.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 7465409401ec94eed8aff812466a2ed1 3 | TextScriptImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /Runtime.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 051682477abcc4504983150e3ebdaf19 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /Runtime/AppTrack.cs: -------------------------------------------------------------------------------- 1 | using System.Collections; 2 | using System.Collections.Generic; 3 | using UnityEngine; 4 | 5 | namespace MS.Log4Unity{ 6 | 7 | public class AppTrack 8 | { 9 | 10 | public static event System.Action handleAppEvent; 11 | 12 | #if UNITY_EDITOR 13 | [UnityEditor.InitializeOnLoadMethod] 14 | private static void OnEditorLaunch(){ 15 | UnityEditor.EditorApplication.playModeStateChanged += (state)=>{ 16 | if(state == UnityEditor.PlayModeStateChange.ExitingEditMode){ 17 | var appEvent = new AppEvent(){ 18 | eventType = AppEventType.ExitingEditMode, 19 | }; 20 | DispatchAppEvent(appEvent); 21 | } 22 | }; 23 | } 24 | 25 | #endif 26 | 27 | [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)] 28 | private static void OnSessionLaunch(){ 29 | Application.quitting += OnSessionQuit; 30 | var appEvent = new AppEvent(){ 31 | eventType = AppEventType.Launch, 32 | }; 33 | DispatchAppEvent(appEvent); 34 | } 35 | 36 | private static void OnSessionQuit(){ 37 | var appEvent = new AppEvent(){ 38 | eventType = AppEventType.Quit, 39 | }; 40 | DispatchAppEvent(appEvent); 41 | } 42 | 43 | private static void DispatchAppEvent(AppEvent evt){ 44 | if(handleAppEvent != null){ 45 | handleAppEvent(evt); 46 | } 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /Runtime/AppTrack.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: df5ca57569f4b48058d82aef485c539f 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Runtime/Appenders.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: c774672323c8b48b3b8bb492c50d1d9c 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /Runtime/Appenders/Appender.cs: -------------------------------------------------------------------------------- 1 | using System.Collections; 2 | using System.Collections.Generic; 3 | using UnityEngine; 4 | using System.Text; 5 | using System.Text.RegularExpressions; 6 | 7 | namespace MS.Log4Unity{ 8 | using Configurations; 9 | 10 | public struct LogEvent{ 11 | public ULogger logger; 12 | public LogType logType; 13 | public object message; 14 | 15 | } 16 | 17 | public interface IAppender 18 | { 19 | 20 | void OnInitialize(ConfigsReader configs); 21 | 22 | bool HandleLogEvent(ref LogEvent logEvent); 23 | 24 | 25 | Env env{ 26 | get;set; 27 | } 28 | } 29 | 30 | public abstract class BaseAppender : IAppender 31 | { 32 | public Env env{ 33 | get;set; 34 | } 35 | 36 | public LogLevel logLevel{ 37 | get;set; 38 | } 39 | 40 | public virtual void OnInitialize(ConfigsReader configs) 41 | { 42 | var envStr = configs.GetString("env",null); 43 | if(envStr == null){ 44 | this.env = Env.All; 45 | }else{ 46 | Env ret; 47 | if(System.Enum.TryParse(envStr,true,out ret)){ 48 | this.env = ret; 49 | }else{ 50 | Debug.LogWarning($"failed to parse {envStr} to Env"); 51 | this.env = Env.All; 52 | } 53 | } 54 | this.logLevel = Configurator.ParseLogLevel(configs.GetString("level",null)); 55 | } 56 | 57 | 58 | public virtual bool HandleLogEvent(ref LogEvent logEvent) 59 | { 60 | return CheckEnv() && CheckLevel(logEvent.logType); 61 | } 62 | 63 | private bool CheckLevel(LogType logType){ 64 | return Configurator.CheckLogOn(logType,this.logLevel); 65 | } 66 | 67 | private bool CheckEnv(){ 68 | #if UNITY_EDITOR 69 | return (this.env & Env.EditorPlayer) == Env.EditorPlayer; 70 | #else 71 | return (this.env & Env.BuiltPlayer) == Env.BuiltPlayer; 72 | #endif 73 | } 74 | } 75 | 76 | 77 | public abstract class LayoutAppender:BaseAppender{ 78 | 79 | private Layout _layout; 80 | public override void OnInitialize(ConfigsReader configs) 81 | { 82 | base.OnInitialize(configs); 83 | if(configs.Has("layout")){ 84 | var dict = configs.GetConfigs("layout"); 85 | _layout = new Layout(dict); 86 | }else{ 87 | _layout = new Layout(); 88 | } 89 | } 90 | 91 | private string FormatMessage(ULogger logger, LogType type, object message){ 92 | return PatternHelper.FormatWithLayout(_layout,logger,type,message); 93 | } 94 | 95 | public override bool HandleLogEvent(ref LogEvent logEvent){ 96 | if(base.HandleLogEvent(ref logEvent)){ 97 | logEvent.message = FormatMessage(logEvent.logger,logEvent.logType,logEvent.message); 98 | return true; 99 | }else{ 100 | return false; 101 | } 102 | } 103 | 104 | } 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | } 114 | -------------------------------------------------------------------------------- /Runtime/Appenders/Appender.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 5a42c8ab87dd04548b7c2d57aed0d1dd 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Runtime/Appenders/AppendersManager.cs: -------------------------------------------------------------------------------- 1 | using System.Collections; 2 | using System.Collections.Generic; 3 | using UnityEngine; 4 | 5 | 6 | namespace MS.Log4Unity{ 7 | 8 | 9 | public static class AppendersRegister{ 10 | private static Dictionary _appenderTypes = new Dictionary(); 11 | 12 | static AppendersRegister(){ 13 | RegisterAppenderType("UnityLogAppender",typeof(UnityLogAppender)); 14 | RegisterAppenderType("FileAppender",typeof(FileAppender)); 15 | RegisterAppenderType("CatagoryFilterAppender",typeof(CatagoryFilterAppender)); 16 | } 17 | 18 | public static void RegisterAppenderType(string typeName,System.Type type){ 19 | _appenderTypes.Add(typeName,type); 20 | } 21 | 22 | internal static System.Type TryGetAppenderType(string typeName){ 23 | System.Type type = null; 24 | if(_appenderTypes.TryGetValue(typeName,out type)){ 25 | return type; 26 | } 27 | return type; 28 | } 29 | 30 | 31 | } 32 | 33 | internal class AppendersManager 34 | { 35 | private static Dictionary _appenders = new Dictionary(); 36 | 37 | private static System.Type TryGetAppenderType(string typeName){ 38 | System.Type type = AppendersRegister.TryGetAppenderType(typeName); 39 | if(type != null){ 40 | return type; 41 | } 42 | string fullTypeName = Configurator.GetAppenderTypeFullName(typeName); 43 | if(fullTypeName != null){ 44 | type = System.Type.GetType(typeName); 45 | } 46 | if(type == null){ 47 | type = System.Type.GetType(typeName); 48 | } 49 | return type; 50 | } 51 | 52 | public static IAppender GetAppender(string appenderName){ 53 | if(_appenders.ContainsKey(appenderName)){ 54 | return _appenders[appenderName]; 55 | } 56 | var appenderCfg = Configurator.GetAppender(appenderName); 57 | if(appenderCfg == null){ 58 | Debug.LogWarning("unknown appender name:" + appenderName); 59 | return null; 60 | } 61 | var tp = TryGetAppenderType(appenderCfg.type); 62 | if(tp == null){ 63 | Debug.LogWarning("unknown appender type:" + appenderCfg.type); 64 | return null; 65 | } 66 | var appender = System.Activator.CreateInstance(tp) as IAppender; 67 | appender.OnInitialize(appenderCfg.GetConfigsReader()); 68 | _appenders.Add(appenderName,appender); 69 | return appender; 70 | } 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /Runtime/Appenders/AppendersManager.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 7f065c95e2350488dbde3c38e0913393 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Runtime/Appenders/CatagoryFilterAppender.cs: -------------------------------------------------------------------------------- 1 | using System.Text.RegularExpressions; 2 | using System.Collections.Generic; 3 | 4 | namespace MS.Log4Unity{ 5 | using Configurations; 6 | public class CatagoryFilterAppender : BaseAppender 7 | { 8 | private List _redirectAppenders = new List(); 9 | private Regex _catagoryRegex; 10 | 11 | public override void OnInitialize(ConfigsReader configs) 12 | { 13 | base.OnInitialize(configs); 14 | var appenderNames = configs.GetStringArray("appenders",null); 15 | if(appenderNames != null){ 16 | foreach(var name in appenderNames){ 17 | var appender = AppendersManager.GetAppender(name); 18 | if(appender != null){ 19 | _redirectAppenders.Add(appender); 20 | } 21 | } 22 | } 23 | var catagory = configs.GetString("catagory",null); 24 | if(catagory != null){ 25 | _catagoryRegex = new Regex(catagory); 26 | } 27 | } 28 | 29 | public override bool HandleLogEvent(ref LogEvent logEvent) 30 | { 31 | if(base.HandleLogEvent(ref logEvent)){ 32 | if(_catagoryRegex != null && !_catagoryRegex.IsMatch(logEvent.logger.catagory)){ 33 | return false; 34 | } 35 | 36 | if(_redirectAppenders != null){ 37 | foreach(var appender in _redirectAppenders){ 38 | var e = logEvent; 39 | appender.HandleLogEvent(ref e); 40 | } 41 | } 42 | return true; 43 | } 44 | return false; 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /Runtime/Appenders/CatagoryFilterAppender.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 7590d484e27cb4ad0a79945ae83015b3 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Runtime/Appenders/FileAppender.cs: -------------------------------------------------------------------------------- 1 | using System.Collections; 2 | using System.Collections.Generic; 3 | using MS.Log4Unity.Configurations; 4 | using UnityEngine; 5 | using System.IO; 6 | using System.Threading.Tasks; 7 | 8 | namespace MS.Log4Unity{ 9 | using FileRoller; 10 | public class FileAppender : LayoutAppender 11 | { 12 | private const string FILE_NAME = "Logs/app.log"; 13 | 14 | private static byte[] _NEW_LINE; 15 | 16 | private static byte[] NEW_LINE{ 17 | get{ 18 | if(_NEW_LINE == null){ 19 | _NEW_LINE = System.Text.Encoding.UTF8.GetBytes("\n"); 20 | } 21 | return _NEW_LINE; 22 | } 23 | } 24 | 25 | /// 26 | /// default max file size is 1MB 27 | /// 28 | public const int DEFAULT_MAX_FILE_SIZE = 1024 * 1024; 29 | public const int DEFAULT_MAX_BACK_UPS = 3; 30 | 31 | public const bool DEFAULT_KEEP_EXT = true; 32 | 33 | 34 | private string _filePath; 35 | private FileStream _fileStream; 36 | 37 | private bool _isFileStreamInitialized = false; 38 | 39 | private int _maxFileCount = 3; 40 | private int _maxFileSize = 1024; 41 | 42 | private int _flushIntervalMillSeconds = 1000; 43 | 44 | private System.DateTime _lastFlushTime; 45 | 46 | 47 | private List _messageQueueToFile = new List(); 48 | private bool _isFileWriting = false; 49 | private BaseRollingFileStream _rollingFS; 50 | 51 | public override void OnInitialize(ConfigsReader configs) 52 | { 53 | base.OnInitialize(configs); 54 | string fileName = configs.GetString("fileName",null); 55 | if(fileName == null){ 56 | fileName = FILE_NAME; 57 | } 58 | if(Path.IsPathRooted(fileName)){ 59 | //absolute path 60 | this._filePath = fileName; 61 | }else{ 62 | //relative path 63 | #if UNITY_EDITOR 64 | this._filePath = fileName; 65 | #else 66 | this._filePath = Path.Combine(Application.persistentDataPath,fileName); 67 | #endif 68 | } 69 | this._maxFileCount = Mathf.Max(1,configs.GetInt("maxBackups",DEFAULT_MAX_BACK_UPS)); 70 | this._maxFileSize = Mathf.Max(10 * 1024,configs.GetInt("maxFileSize",DEFAULT_MAX_FILE_SIZE)); 71 | _flushIntervalMillSeconds = Mathf.Max(0,configs.GetInt("flushInterval",1000)); 72 | bool keepExt = configs.GetBool("keepExt",DEFAULT_KEEP_EXT); 73 | 74 | RollType rollType = RollType.Session; 75 | var rollTypeStr = configs.GetString("rollType",null); 76 | if(rollTypeStr != null){ 77 | if(!System.Enum.TryParse(rollTypeStr,out rollType)){ 78 | UnityEngine.Debug.LogWarning($"bad rollType:{rollTypeStr}"); 79 | rollType = RollType.Session; 80 | } 81 | } 82 | AppTrack.handleAppEvent += HandleAppEvent; 83 | if(rollType == RollType.Session){ 84 | _rollingFS = new IndexedRollingFileStream(_filePath,new IndexedRollingFileStream.Options(){ 85 | maxFileSize = _maxFileSize, 86 | maxBackups = _maxFileCount, 87 | keepExts = keepExt 88 | }); 89 | _rollingFS.Roll(); 90 | }else if(rollType == RollType.Date){ 91 | _rollingFS = new DateRollingFileStream(_filePath,new DateRollingFileStream.Options(){ 92 | maxBackups = _maxFileCount, 93 | maxFileSize = _maxFileSize, 94 | keepExt = keepExt 95 | }); 96 | }else if(rollType == RollType.Size){ 97 | _rollingFS = new IndexedRollingFileStream(_filePath,new IndexedRollingFileStream.Options(){ 98 | maxFileSize = _maxFileSize, 99 | maxBackups = _maxFileCount, 100 | keepExts = keepExt 101 | }); 102 | } 103 | } 104 | 105 | public override bool HandleLogEvent(ref LogEvent logEvent){ 106 | if(!base.HandleLogEvent(ref logEvent)){ 107 | return false; 108 | } 109 | if(_rollingFS == null){ 110 | return false; 111 | } 112 | QueueMessage(logEvent.message); 113 | return true; 114 | } 115 | 116 | private void QueueMessage(object message){ 117 | var empty = _messageQueueToFile.Count == 0; 118 | _messageQueueToFile.Add(message); 119 | if(!_isFileWriting){ 120 | WriteMessageToFileAsync(); 121 | } 122 | } 123 | 124 | 125 | private async void WriteMessageToFileAsync(){ 126 | _isFileWriting = true; 127 | while(_messageQueueToFile.Count > 0 ){ 128 | var message = _messageQueueToFile[0]; 129 | _messageQueueToFile.RemoveAt(0); 130 | if(message == null){ 131 | message = ""; 132 | } 133 | var bytes = System.Text.Encoding.UTF8.GetBytes(message.ToString()); 134 | await _rollingFS.WriteAsync(bytes,0,bytes.Length); 135 | await _rollingFS.WriteAsync(NEW_LINE,0,NEW_LINE.Length); 136 | } 137 | _isFileWriting = false; 138 | var deltaTime = System.DateTime.Now - _lastFlushTime; 139 | if(deltaTime.Milliseconds < _flushIntervalMillSeconds){ 140 | await Task.Delay((int)(_flushIntervalMillSeconds - deltaTime.Milliseconds)); 141 | } 142 | _lastFlushTime = System.DateTime.Now; 143 | await _rollingFS.FlushAsync(); 144 | } 145 | 146 | private void HandleAppEvent(AppEvent appEvent) 147 | { 148 | if(appEvent.eventType == AppEventType.Quit || 149 | appEvent.eventType == AppEventType.ExitingEditMode){ 150 | _messageQueueToFile.Clear(); 151 | _rollingFS.Close(); 152 | } 153 | } 154 | 155 | 156 | public enum RollType{ 157 | Size, 158 | Session, 159 | Date, 160 | } 161 | 162 | } 163 | } 164 | -------------------------------------------------------------------------------- /Runtime/Appenders/FileAppender.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 092851dc1e1a2458bbd5c245ce19fe92 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Runtime/Appenders/UnityLogAppender.cs: -------------------------------------------------------------------------------- 1 | using System.Collections; 2 | using System.Collections.Generic; 3 | using UnityEngine; 4 | 5 | namespace MS.Log4Unity{ 6 | public class UnityLogAppender : LayoutAppender 7 | { 8 | 9 | public override bool HandleLogEvent(ref LogEvent logEvent){ 10 | if(!base.HandleLogEvent(ref logEvent)){ 11 | return false; 12 | } 13 | var type = logEvent.logType; 14 | var message = logEvent.message; 15 | switch(type){ 16 | case LogType.Debug: 17 | case LogType.Info: 18 | Debug.Log(message); 19 | break; 20 | case LogType.Warn: 21 | Debug.LogWarning(message); 22 | break; 23 | case LogType.Error: 24 | Debug.LogError(message); 25 | break; 26 | case LogType.Fatal: 27 | Debug.LogError(message); 28 | break; 29 | } 30 | return true; 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /Runtime/Appenders/UnityLogAppender.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: c8f87504f2fec4975a96ad6fa570d882 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Runtime/ConditionalLogger.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics; 2 | namespace MS.Log4Unity{ 3 | 4 | 5 | public class ConditionalLogger 6 | { 7 | 8 | private ULogger _logger; 9 | 10 | public ConditionalLogger(ULogger logger){ 11 | _logger = logger; 12 | } 13 | 14 | public ULogger innerLogger{ 15 | get{ 16 | return _logger; 17 | } 18 | } 19 | 20 | [Conditional("UNITY_EDITOR")] 21 | public void EditorDebug(object message){ 22 | innerLogger.Debug(message); 23 | } 24 | 25 | [Conditional("UNITY_EDITOR")] 26 | public void EditorInfo(object message){ 27 | innerLogger.Info(message); 28 | } 29 | 30 | [Conditional("UNITY_EDITOR")] 31 | public void EditorWarn(object message){ 32 | innerLogger.Warn(message); 33 | } 34 | 35 | [Conditional("UNITY_EDITOR")] 36 | public void EditorError(object message){ 37 | innerLogger.Error(message); 38 | } 39 | 40 | [Conditional("UNITY_EDITOR")] 41 | public void EditorFatal(object message){ 42 | innerLogger.Fatal(message); 43 | } 44 | 45 | [Conditional("LOG4UNITY_DEBUG")] 46 | public void Debug(object message) 47 | { 48 | innerLogger.Debug(message); 49 | } 50 | 51 | [Conditional("LOG4UNITY_INFO")] 52 | public void Info(object message) 53 | { 54 | innerLogger.Info(message); 55 | } 56 | 57 | [Conditional("LOG4UNITY_WARN")] 58 | public void Warn(object message) 59 | { 60 | innerLogger.Warn(message); 61 | } 62 | public void Error(object message) 63 | { 64 | innerLogger.Error(message); 65 | } 66 | public void Fatal(object message) 67 | { 68 | innerLogger.Fatal(message); 69 | } 70 | 71 | public static bool isDebugDefined{ 72 | get{ 73 | #if LOG4UNITY_DEBUG 74 | return true; 75 | #else 76 | return false; 77 | #endif 78 | } 79 | } 80 | 81 | public static bool isInfoDefined{ 82 | get{ 83 | #if LOG4UNITY_INFO 84 | return true; 85 | #else 86 | return false; 87 | #endif 88 | } 89 | } 90 | 91 | public static bool isWarnDefined{ 92 | get{ 93 | #if LOG4UNITY_WARN 94 | return true; 95 | #else 96 | return false; 97 | #endif 98 | } 99 | } 100 | 101 | 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /Runtime/ConditionalLogger.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 025e487c9474f4db8b073a3992feebb1 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Runtime/Configurator.cs: -------------------------------------------------------------------------------- 1 | using System.Collections; 2 | using System.Collections.Generic; 3 | using UnityEngine; 4 | using LitJson; 5 | using System.IO; 6 | using System.Text.RegularExpressions; 7 | 8 | namespace MS.Log4Unity{ 9 | 10 | using Configurations; 11 | 12 | 13 | internal class Configurator 14 | { 15 | public const string DEFAULT_CATAGORY = "default"; 16 | 17 | private static Dictionary _exactCatagories = new Dictionary(); 18 | private static List _fuzzyCatagories = new List(); 19 | 20 | 21 | private static Configuration _configuration; 22 | 23 | public static bool TryLoad(string configFile){ 24 | if(!File.Exists(configFile)){ 25 | return false; 26 | } 27 | var text = File.ReadAllText(configFile); 28 | return TryLoadFromText(text); 29 | } 30 | 31 | public static void Load(string configFile){ 32 | if(!TryLoad(configFile)){ 33 | throw new System.Exception($"configFile not found at {configFile}"); 34 | } 35 | } 36 | 37 | public static bool TryLoadFromResources(string configFile){ 38 | var textAsset = Resources.Load(configFile); 39 | if(!textAsset){ 40 | return false; 41 | } 42 | return TryLoadFromText(textAsset.text); 43 | } 44 | 45 | public static void LoadFromResources(string configFile){ 46 | if(!TryLoadFromResources(configFile)){ 47 | throw new System.Exception($"configFile not found at Resources/{configFile}"); 48 | } 49 | } 50 | 51 | public static void LoadFromText(string configText){ 52 | TryLoadFromText(configText); 53 | if(_configuration == null){ 54 | _configuration = new Configuration(); 55 | OnConfigurationLoadSuccess(); 56 | } 57 | } 58 | 59 | public static bool TryLoadFromText(string configText){ 60 | try{ 61 | _configuration = JsonMapper.ToObject(configText); 62 | OnConfigurationLoadSuccess(); 63 | return true; 64 | }catch(System.Exception e){ 65 | Debug.LogException(e); 66 | return false; 67 | } 68 | } 69 | 70 | private static void OnConfigurationLoadSuccess(){ 71 | _exactCatagories.Clear(); 72 | _fuzzyCatagories.Clear(); 73 | if(_configuration.catagories == null){ 74 | return; 75 | } 76 | foreach(var c in _configuration.catagories){ 77 | if(c.name == null){ 78 | Debug.LogWarning("name required for catagory"); 79 | continue; 80 | } 81 | if(c.matchType == null || c.matchType == "exact"){ 82 | _exactCatagories.Add(c.name,c); 83 | }else if(c.matchType == "regex"){ 84 | _fuzzyCatagories.Add(new RegexPatternCatagory(c)); 85 | }else{ 86 | Debug.LogWarning($"unknown matchType {c.matchType}"); 87 | } 88 | } 89 | } 90 | 91 | private static Catagory GetCatagory(string name){ 92 | if(_exactCatagories.ContainsKey(name)){ 93 | return _exactCatagories[name]; 94 | } 95 | foreach(var c in _fuzzyCatagories){ 96 | if(c.regex.IsMatch(name)){ 97 | return c.catagory; 98 | } 99 | } 100 | return null; 101 | } 102 | 103 | 104 | 105 | public static Appender GetAppender(string name){ 106 | if(!CheckConfigFileLoaded()){ 107 | return null; 108 | } 109 | Appender ret = null; 110 | if(_configuration.appenders.TryGetValue(name,out ret)){ 111 | return ret; 112 | }else{ 113 | Debug.LogWarning("can not find appender by name = " + name); 114 | } 115 | return null; 116 | } 117 | 118 | private static bool IsConfigurationLoaded(){ 119 | return _configuration != null; 120 | } 121 | 122 | private static bool _notConfiguratedWarningShowed = false; 123 | private static bool _hasTriedLoadDefaultConfigFile = false; 124 | 125 | private static bool CheckConfigFileLoaded(){ 126 | if(!IsConfigurationLoaded()){ 127 | if(!_notConfiguratedWarningShowed){ 128 | Debug.LogWarning("config file must be loaded first before you call any api of logger!"); 129 | _notConfiguratedWarningShowed = true; 130 | } 131 | return false; 132 | } 133 | return true; 134 | } 135 | 136 | private static bool TryLoadDefaultConfigIfNot(){ 137 | if(!IsConfigurationLoaded()){ 138 | if(!_hasTriedLoadDefaultConfigFile){ 139 | _hasTriedLoadDefaultConfigFile = true; 140 | if(TryLoadFromResources("log4unity")){ 141 | return true; 142 | } 143 | } 144 | if(!_notConfiguratedWarningShowed){ 145 | Debug.LogWarning("config file must be loaded first before you call any api of logger!"); 146 | _notConfiguratedWarningShowed = true; 147 | } 148 | return false; 149 | } 150 | return true; 151 | } 152 | 153 | public static string GetAppenderTypeFullName(string appenderTypeName){ 154 | if(!TryLoadDefaultConfigIfNot()){ 155 | return null; 156 | } 157 | if(_configuration.appenderTypesRegister == null){ 158 | return null; 159 | } 160 | string fullName; 161 | if(_configuration.appenderTypesRegister.TryGetValue(appenderTypeName,out fullName)){ 162 | return fullName; 163 | } 164 | return null; 165 | } 166 | 167 | public static void ConfigurateLogger(ULogger logger,string catagoryName){ 168 | if(!TryLoadDefaultConfigIfNot()){ 169 | return; 170 | } 171 | var catagory = Configurator.GetCatagory(catagoryName); 172 | if(catagory == null){ 173 | catagory = Configurator.GetCatagory(DEFAULT_CATAGORY); 174 | } 175 | if(catagory == null){ 176 | //missing default catagory 177 | return; 178 | } 179 | logger.ClearAppenders(); 180 | var level = ParseLogLevel(catagory.level); 181 | logger.level = level; 182 | if(catagory.appenders == null || catagory.appenders.Length == 0){ 183 | //default 184 | }else{ 185 | foreach(var ap in catagory.appenders){ 186 | var appender = AppendersManager.GetAppender(ap); 187 | if(appender != null){ 188 | logger.Append(appender); 189 | } 190 | } 191 | } 192 | } 193 | 194 | internal static LogLevel ParseLogLevel(string level){ 195 | if(level == null){ 196 | return LogLevel.All; 197 | } 198 | LogLevel ret; 199 | if(System.Enum.TryParse(level,true,out ret)){ 200 | return ret; 201 | }else{ 202 | Debug.LogWarning($"failed to parse {level} to LogLevel"); 203 | return LogLevel.All; 204 | } 205 | } 206 | 207 | /// 208 | /// check the logType should on or off in the specific logLevel 209 | /// 210 | public static bool CheckLogOn(LogType logType,LogLevel level){ 211 | var lv = (LogLevel)logType; 212 | return lv <= level; 213 | } 214 | 215 | 216 | private class RegexPatternCatagory{ 217 | public readonly Catagory catagory; 218 | public readonly Regex regex; 219 | 220 | public RegexPatternCatagory(Catagory catagory){ 221 | this.catagory = catagory; 222 | regex = new Regex(catagory.name); 223 | } 224 | } 225 | } 226 | 227 | [System.Flags] 228 | public enum Env{ 229 | EditorPlayer = 1, 230 | BuiltPlayer = 2, 231 | All = 3, 232 | } 233 | 234 | public struct AppEvent{ 235 | public AppEventType eventType; 236 | } 237 | 238 | public enum AppEventType{ 239 | 240 | Launch, 241 | Quit, 242 | /// 243 | /// this event only happen in UnityEditor 244 | /// 245 | ExitingEditMode, 246 | } 247 | 248 | 249 | namespace Configurations{ 250 | 251 | [UnityEngine.Scripting.Preserve] 252 | public class Configuration{ 253 | 254 | public Dictionary appenderTypesRegister; 255 | public Dictionary appenders; 256 | public List catagories; 257 | } 258 | 259 | 260 | public class Layout{ 261 | public string type; 262 | public string pattern; 263 | 264 | public Layout(){ 265 | this.type = "basic"; 266 | } 267 | 268 | public Layout(ConfigsReader configs){ 269 | this.type = configs.GetString("type","basic"); 270 | this.pattern = configs.GetString("pattern",null); 271 | } 272 | } 273 | 274 | 275 | public class Appender{ 276 | public string type; 277 | public JsonData configs; 278 | 279 | private ConfigsReader _configsReader; 280 | 281 | public ConfigsReader GetConfigsReader(){ 282 | if(_configsReader == null){ 283 | _configsReader = new ConfigsReader(configs); 284 | } 285 | return _configsReader; 286 | } 287 | 288 | 289 | 290 | } 291 | 292 | public class Catagory{ 293 | public string[] appenders; 294 | public string level; 295 | public string name; 296 | public string matchType; 297 | 298 | } 299 | 300 | 301 | public class ConfigsReader{ 302 | 303 | private JsonData _configs; 304 | 305 | internal ConfigsReader(JsonData configs){ 306 | _configs = configs; 307 | } 308 | 309 | public bool Has(string key){ 310 | return _configs.ContainsKey(key); 311 | } 312 | 313 | public string[] GetStringArray(string key,string[] defaultValue){ 314 | if(!Has(key)){ 315 | return defaultValue; 316 | } 317 | var jsonData = _configs[key]; 318 | if(!jsonData.IsArray){ 319 | return defaultValue; 320 | } 321 | var result = new string[jsonData.Count]; 322 | for(var i = 0; i < jsonData.Count;i ++){ 323 | result[i] = jsonData[i].ToString(); 324 | } 325 | return result; 326 | } 327 | 328 | public int GetInt(string key,int defaultValue){ 329 | if(!Has(key)){ 330 | return defaultValue; 331 | } 332 | var value = _configs[key]; 333 | return (int)value; 334 | } 335 | 336 | public string GetString(string key,string defaultValue){ 337 | if(!Has(key)){ 338 | return defaultValue; 339 | } 340 | var value = _configs[key]; 341 | return (string)value; 342 | } 343 | 344 | public bool GetBool(string key,bool defaultValue){ 345 | if(!Has(key)){ 346 | return defaultValue; 347 | } 348 | var value = _configs[key]; 349 | return (bool)value; 350 | } 351 | 352 | public ConfigsReader GetConfigs(string key){ 353 | if(!Has(key)){ 354 | return null; 355 | } 356 | var value = _configs[key]; 357 | return new ConfigsReader(value); 358 | 359 | } 360 | 361 | 362 | } 363 | } 364 | } 365 | 366 | 367 | 368 | 369 | -------------------------------------------------------------------------------- /Runtime/Configurator.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 2d82210657cd8437887ca3ca862cf29d 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Runtime/LogFactory.cs: -------------------------------------------------------------------------------- 1 | using System.Collections; 2 | using System.Collections.Generic; 3 | using UnityEngine; 4 | 5 | 6 | namespace MS.Log4Unity{ 7 | 8 | 9 | 10 | 11 | public static class LogFactory 12 | { 13 | 14 | private static Dictionary _cachedLoggers = new Dictionary(); 15 | private static Dictionary _cachedConditionalLogger = new Dictionary(); 16 | static LogFactory(){ 17 | } 18 | 19 | public static bool ConfigurateAtResources(string file){ 20 | return Configurator.TryLoadFromResources(file); 21 | } 22 | 23 | public static bool Configurate(string configJSONText){ 24 | return Configurator.TryLoadFromText(configJSONText); 25 | } 26 | 27 | public static ULogger GetLogger(){ 28 | return GetLogger(Configurator.DEFAULT_CATAGORY); 29 | } 30 | 31 | public static ULogger GetLogger(string catagoryName){ 32 | ULogger logger = null; 33 | if(_cachedLoggers.TryGetValue(catagoryName,out logger)){ 34 | return logger; 35 | } 36 | logger = new ULoggerDefault(catagoryName); 37 | return logger; 38 | } 39 | 40 | public static ULogger GetLogger(System.Type type){ 41 | return GetLogger(type.FullName); 42 | } 43 | 44 | public static ULogger GetLogger(){ 45 | return GetLogger(typeof(T)); 46 | } 47 | 48 | public static ConditionalLogger GetConditionalLogger(){ 49 | return GetLogger().Conditional(); 50 | } 51 | 52 | public static ConditionalLogger GetConditionalLogger(string catagory){ 53 | return GetLogger(catagory).Conditional(); 54 | } 55 | 56 | public static ConditionalLogger GetConditionalLogger(System.Type type){ 57 | return GetLogger(type).Conditional(); 58 | } 59 | 60 | public static ConditionalLogger GetConditionalLogger(){ 61 | return GetLogger().Conditional(); 62 | } 63 | 64 | 65 | 66 | public static ConditionalLogger Conditional(this ULogger logger){ 67 | ConditionalLogger result = null; 68 | if(_cachedConditionalLogger.TryGetValue(logger,out result)){ 69 | return result; 70 | }else{ 71 | result = new ConditionalLogger(logger); 72 | _cachedConditionalLogger.Add(logger,result); 73 | return result; 74 | } 75 | } 76 | 77 | 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /Runtime/LogFactory.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 38a7a20112b1a44ab9b761f8e11d9f2f 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Runtime/Logger.cs: -------------------------------------------------------------------------------- 1 |  2 | 3 | using System.Collections; 4 | using System.Collections.Generic; 5 | using System.Diagnostics; 6 | 7 | namespace MS.Log4Unity{ 8 | 9 | public enum LogType{ 10 | Fatal = 1, 11 | Error = 2, 12 | Warn = 4, 13 | Info = 8, 14 | Debug = 16, 15 | } 16 | 17 | [System.Flags] 18 | public enum LogLevel{ 19 | Off = 0, 20 | Fatal = 1, 21 | Error = 2, 22 | Warn = 4, 23 | Info = 8, 24 | Debug = 16, 25 | All = 31, 26 | } 27 | 28 | public interface ULogger{ 29 | 30 | void Debug(object message); 31 | 32 | void Info(object message); 33 | 34 | void Warn(object message); 35 | 36 | void Error(object message); 37 | 38 | void Fatal(object message); 39 | 40 | void ClearAppenders(); 41 | 42 | string catagory{ 43 | get; 44 | } 45 | 46 | bool IsOn(LogType type); 47 | 48 | LogLevel level{ 49 | get;set; 50 | } 51 | 52 | void Append(IAppender appender); 53 | } 54 | 55 | public class ULoggerDefault:ULogger 56 | { 57 | private List _appenders = new List(); 58 | 59 | private LogLevel _logLevel = LogLevel.All; 60 | 61 | private string _catagory; 62 | 63 | private bool _isConfigurated = false; 64 | 65 | public ULoggerDefault(string catagory){ 66 | _catagory = catagory; 67 | } 68 | 69 | private void ConfigurateIfNot(){ 70 | if(_isConfigurated){ 71 | return; 72 | } 73 | _isConfigurated = true; 74 | Configurator.ConfigurateLogger(this,this.catagory); 75 | } 76 | 77 | public void Append(IAppender appender) 78 | { 79 | ConfigurateIfNot(); 80 | _appenders.Add(appender); 81 | } 82 | 83 | public string catagory{ 84 | get{ 85 | return _catagory; 86 | } 87 | } 88 | 89 | public LogLevel level{ 90 | set{ 91 | ConfigurateIfNot(); 92 | _logLevel = value; 93 | }get{ 94 | ConfigurateIfNot(); 95 | return _logLevel; 96 | } 97 | } 98 | 99 | public bool IsOn(LogType type){ 100 | ConfigurateIfNot(); 101 | return Configurator.CheckLogOn(type,_logLevel); 102 | } 103 | 104 | private void HandleMessage(LogType type,object message){ 105 | foreach(var ap in _appenders){ 106 | var logEvent = new LogEvent(){ 107 | logger = this, 108 | logType = type, 109 | message = message, 110 | }; 111 | ap.HandleLogEvent(ref logEvent); 112 | } 113 | } 114 | 115 | public void Debug(object message) 116 | { 117 | if(!IsOn(LogType.Debug)){ 118 | return; 119 | } 120 | HandleMessage(LogType.Debug,message); 121 | } 122 | 123 | public void Info(object message) 124 | { 125 | if(!IsOn(LogType.Info)){ 126 | return; 127 | } 128 | HandleMessage(LogType.Info,message); 129 | } 130 | 131 | public void Warn(object message) 132 | { 133 | if(!IsOn(LogType.Warn)){ 134 | return; 135 | } 136 | HandleMessage(LogType.Warn,message); 137 | } 138 | 139 | public void Error(object message) 140 | { 141 | if(!IsOn(LogType.Error)){ 142 | return; 143 | } 144 | HandleMessage(LogType.Error,message); 145 | } 146 | 147 | public void Fatal(object message) 148 | { 149 | if(!IsOn(LogType.Fatal)){ 150 | return; 151 | } 152 | HandleMessage(LogType.Fatal,message); 153 | } 154 | 155 | public void ClearAppenders() 156 | { 157 | ConfigurateIfNot(); 158 | _appenders.Clear(); 159 | } 160 | 161 | } 162 | 163 | 164 | 165 | } 166 | 167 | -------------------------------------------------------------------------------- /Runtime/Logger.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 0e4d32dd7bf0d4c688c9896f4df6c201 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Runtime/MS.Log4Unity.asmdef: -------------------------------------------------------------------------------- 1 | { 2 | "name": "MS.Log4Unity", 3 | "references": [ 4 | "MS.FileRoller" 5 | ], 6 | "optionalUnityReferences": [], 7 | "includePlatforms": [], 8 | "excludePlatforms": [], 9 | "allowUnsafeCode": false, 10 | "overrideReferences": false, 11 | "precompiledReferences": [], 12 | "autoReferenced": true, 13 | "defineConstraints": [] 14 | } -------------------------------------------------------------------------------- /Runtime/MS.Log4Unity.asmdef.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: ce8bcc7ee73d7425684c794ce8b377e1 3 | AssemblyDefinitionImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /Runtime/PatternHelper.cs: -------------------------------------------------------------------------------- 1 | using System.Collections; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace MS.Log4Unity{ 6 | using Configurations; 7 | 8 | public struct PatternFieldResolveContext{ 9 | public ULogger logger; 10 | public LogType logType; 11 | public string message; 12 | public string parameter; 13 | } 14 | 15 | public delegate string PatternFieldResolve(PatternFieldResolveContext context); 16 | 17 | 18 | public class PatternHelper 19 | { 20 | 21 | private static Dictionary _resolverMap = new Dictionary(); 22 | private static PatternFormatter _formatter = new PatternFormatter(); 23 | private static Dictionary _layouts = new Dictionary(); 24 | private static Dictionary _logTypeToColor = new Dictionary(){ 25 | {LogType.Debug,"#4169E1"}, 26 | {LogType.Info,null}, 27 | {LogType.Warn,"yellow"}, 28 | {LogType.Error,"red"}, 29 | {LogType.Fatal,"magenta"}, 30 | }; 31 | 32 | static PatternHelper(){ 33 | 34 | //register specific field 35 | RegisterPatternFieldResolver('c',(ctx)=>{ 36 | return ctx.logger.catagory; 37 | }); 38 | RegisterPatternFieldResolver('r',(ctx)=>{ 39 | return System.DateTime.Now.ToLocalTime().ToLongTimeString(); 40 | }); 41 | RegisterPatternFieldResolver('p',(ctx)=>{ 42 | return ctx.logType.ToString(); 43 | }); 44 | RegisterPatternFieldResolver('m',(ctx)=>{ 45 | return ctx.message; 46 | }); 47 | RegisterPatternFieldResolver('d',(ctx)=>{ 48 | return System.DateTime.Now.ToString(ctx.parameter); 49 | }); 50 | RegisterPatternFieldResolver('n',(ctx)=>{ 51 | return "\n"; 52 | }); 53 | //start color block 54 | RegisterPatternFieldResolver('[',(ctx)=>{ 55 | string c = TryGetColor(ctx.logType); 56 | if(c == null){ 57 | return null; 58 | } 59 | return string.Format("",c); 60 | }); 61 | //end color block 62 | RegisterPatternFieldResolver(']',(ctx)=>{ 63 | string c = TryGetColor(ctx.logType); 64 | if(c == null){ 65 | return null; 66 | } 67 | return ""; 68 | }); 69 | 70 | 71 | //register builtin layout 72 | 73 | RegisterLayout(new Layout(){ 74 | type = "basic", 75 | pattern = "[%p] %c - %m" 76 | }); 77 | 78 | RegisterLayout(new Layout(){ 79 | type = "basic-time", 80 | pattern = "[%d] [%p] %c - %m", 81 | }); 82 | 83 | RegisterLayout(new Layout(){ 84 | type = "coloured", 85 | pattern = "%[[%p] %c%] - %m" 86 | }); 87 | 88 | RegisterLayout(new Layout(){ 89 | type = "coloured-time", 90 | pattern = "%[[%d] [%p] %c%] - %m" 91 | }); 92 | 93 | RegisterLayout(new Layout(){ 94 | type = "messagePassThrough", 95 | pattern = null, 96 | }); 97 | } 98 | 99 | public static string TryGetColor(LogType logType){ 100 | string color = null; 101 | if(_logTypeToColor.TryGetValue(logType,out color)){ 102 | } 103 | return color; 104 | } 105 | 106 | public static void RegisterPatternFieldResolver(char specificChar,PatternFieldResolve resolver){ 107 | _resolverMap.Add(specificChar,resolver); 108 | } 109 | 110 | public static void RegisterLayout(Layout layout){ 111 | _layouts.Add(layout.type,layout); 112 | } 113 | 114 | private static PatternFieldResolve TryGetResolver(char specificChar){ 115 | PatternFieldResolve v = null; 116 | if(_resolverMap.TryGetValue(specificChar,out v)){ 117 | return v; 118 | } 119 | return null; 120 | } 121 | 122 | public static string Format(string pattern, ULogger logger, LogType type, object message){ 123 | return _formatter.Format(pattern,logger,type,message); 124 | } 125 | 126 | public static string FormatWithLayout(Layout layout, ULogger logger, LogType type, object message){ 127 | var messageStr = message == null?"":message.ToString(); 128 | Layout actualLayout = null; 129 | if(layout.type == "pattern"){ 130 | actualLayout = layout; 131 | }else{ 132 | if(!_layouts.TryGetValue(layout.type,out actualLayout)){ 133 | return messageStr; 134 | } 135 | } 136 | if(actualLayout == null){ 137 | return messageStr; 138 | } 139 | return Format(actualLayout.pattern,logger,type,message); 140 | } 141 | 142 | 143 | 144 | private class PatternFormatter{ 145 | 146 | private const char ESCAPE_CHAR = '%'; 147 | private const char PARAMETER_BEGIN_CHAR = '{'; 148 | private const char PARAMETER_END_CHAR = '}'; 149 | 150 | private int _index; 151 | private string _pattern; 152 | 153 | private PatternFieldResolve _fieldResolver; 154 | private StringBuilder _messageBuilder = null; 155 | private StringBuilder _fieldParameterBuilder; 156 | 157 | 158 | public PatternFormatter(){ 159 | 160 | } 161 | 162 | private bool MoveNext(){ 163 | _index ++; 164 | return _index < _pattern.Length; 165 | } 166 | 167 | public char charValue{ 168 | get{ 169 | return _pattern[_index]; 170 | } 171 | } 172 | 173 | 174 | private void BeginParseField(ref PatternFieldResolveContext ctx){ 175 | ctx.parameter = null; 176 | var vName = this.charValue; 177 | _fieldResolver = TryGetResolver(vName); 178 | } 179 | 180 | private bool IsParsingField(){ 181 | return _fieldResolver != null; 182 | } 183 | 184 | 185 | private void EndParseField(ref PatternFieldResolveContext ctx){ 186 | var value = _fieldResolver(ctx); 187 | if(value != null){ 188 | _messageBuilder.Append(value); 189 | } 190 | _fieldResolver = null; 191 | } 192 | 193 | private void BeginParseFieldParameter(){ 194 | _fieldParameterBuilder = new StringBuilder(); 195 | } 196 | 197 | private bool IsParsingFieldParameter(){ 198 | return _fieldParameterBuilder != null; 199 | } 200 | 201 | private string EndParseFieldParameter(){ 202 | var s = _fieldParameterBuilder.ToString(); 203 | _fieldParameterBuilder = null; 204 | return s; 205 | } 206 | 207 | 208 | private bool ReadCharValueIfNotParsingField(ref PatternFieldResolveContext context){ 209 | var charValue = this.charValue; 210 | if(charValue == ESCAPE_CHAR){ 211 | if(!MoveNext()){ 212 | return false; 213 | } 214 | this.BeginParseField(ref context); 215 | }else{ 216 | _messageBuilder.Append(charValue); 217 | } 218 | return true; 219 | } 220 | 221 | public string Format(string pattern, ULogger logger, LogType type, object message){ 222 | var messageStr = message == null? "":message.ToString(); 223 | if(pattern == null){ 224 | return messageStr; 225 | } 226 | _index = -1; 227 | _pattern = pattern; 228 | _messageBuilder = new StringBuilder(); 229 | _fieldParameterBuilder = null; 230 | _fieldResolver = null; 231 | var context = new PatternFieldResolveContext(){ 232 | logger = logger, 233 | logType = type, 234 | message = messageStr 235 | }; 236 | 237 | while (true) 238 | { 239 | if(!MoveNext()){ 240 | break; 241 | } 242 | var charValue = this.charValue; 243 | if(this.IsParsingField()){ 244 | if(this.IsParsingFieldParameter()){ 245 | if(charValue == PARAMETER_END_CHAR ){ 246 | var p = this.EndParseFieldParameter(); 247 | context.parameter = p; 248 | }else{ 249 | _fieldParameterBuilder.Append(charValue); 250 | } 251 | }else{ 252 | if(charValue == PARAMETER_BEGIN_CHAR){ 253 | BeginParseFieldParameter(); 254 | }else{ 255 | EndParseField(ref context); 256 | if(!ReadCharValueIfNotParsingField(ref context)){ 257 | break; 258 | } 259 | } 260 | } 261 | }else{ 262 | if(!ReadCharValueIfNotParsingField(ref context)){ 263 | break; 264 | } 265 | } 266 | } 267 | if(IsParsingField()){ 268 | EndParseField(ref context); 269 | } 270 | return _messageBuilder.ToString(); 271 | } 272 | } 273 | } 274 | } 275 | -------------------------------------------------------------------------------- /Runtime/PatternHelper.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: f164f0bebfd104b3e84677dc025debc3 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name":"com.ms.log4unity", 3 | "display":"Log4Unity", 4 | "version":"0.4.1", 5 | "description":"Log4Unity", 6 | "dependencies": { 7 | "com.ms.fileroller":"0.1.0" 8 | } 9 | } -------------------------------------------------------------------------------- /package.json.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 8a894a3f08eef44488b49daf403bcff9 3 | TextScriptImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | --------------------------------------------------------------------------------