| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384 |
- using System;
- using System.IO;
- using System.Threading;
- namespace Aivfo.OperationLog
- {
- /// <summary>
- /// 配置热加载(G3-3 / 14§10):后台定时轮询一个 JSON 配置文件,文件变化即热应用到
- /// <see cref="OperationLogOptions"/>(全局开关/级别 + 模块级开关),无需改代码重编。
- /// <para>
- /// "集中下发"落地方式:日志微服务 / 运维把统一配置文件下发(覆盖)到各端
- /// <see cref="OperationLogOptions.ConfigFilePath"/> 指向的路径即可;本组件负责读取并热生效。
- /// </para>
- /// 全 try 兜底:读/解析失败沿用旧配置,绝不抛、绝不影响业务。
- /// </summary>
- internal sealed class OperationLogConfigWatcher : IDisposable
- {
- private readonly OperationLogOptions _options;
- private readonly Action<string> _onSelfError;
- private readonly Timer _timer;
- private DateTime _lastWriteUtc = DateTime.MinValue;
- private long _lastLength = -1;
- private volatile bool _disposed;
- public OperationLogConfigWatcher(OperationLogOptions options, Action<string> onSelfError)
- {
- _options = options;
- _onSelfError = onSelfError;
- var periodMs = Math.Max(3, _options.ConfigReloadSeconds) * 1000;
- // 启动即先读一次(dueTime=0),随后周期轮询。
- _timer = new Timer(Poll, null, 0, periodMs);
- }
- private void Poll(object state)
- {
- if (_disposed) return;
- try
- {
- var path = _options.ConfigFilePath;
- if (string.IsNullOrWhiteSpace(path) || !File.Exists(path)) return;
- // 仅在 文件大小或写时间 变化时才重读重应用(省 IO、避免无谓刷新)。
- var info = new FileInfo(path);
- var writeUtc = info.LastWriteTimeUtc;
- var len = info.Length;
- if (writeUtc == _lastWriteUtc && len == _lastLength) return;
- string json = SafeReadAllText(path);
- if (json == null) return;
- _options.ApplyConfigJson(json);
- _lastWriteUtc = writeUtc;
- _lastLength = len;
- _onSelfError?.Invoke($"操作日志配置已热加载: {path}");
- }
- catch (Exception ex)
- {
- try { _onSelfError?.Invoke("配置热加载失败(沿用旧配置): " + ex.Message); } catch { }
- }
- }
- /// <summary>容错读取:文件可能正被写,读失败返回 null(下轮再试)。</summary>
- private static string SafeReadAllText(string path)
- {
- try
- {
- using (var fs = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
- using (var sr = new StreamReader(fs, System.Text.Encoding.UTF8))
- {
- return sr.ReadToEnd();
- }
- }
- catch { return null; }
- }
- public void Dispose()
- {
- if (_disposed) return;
- _disposed = true;
- try { _timer?.Dispose(); } catch { /* ignore */ }
- }
- }
- }
|