1 //
2 // Copyright (c) 2010-2025 Antmicro
3 // Copyright (c) 2011-2015 Realtime Embedded
4 //
5 // This file is licensed under the MIT License.
6 // Full license text is available in 'licenses/MIT.txt'.
7 //
8 
9 // this strange construct is to globally enable/disable `Trace` methods using single #define here;
10 // normally each file calling `Trace` should have it defined either on its own or globally in csproj/command line;
11 #if DEBUG
12 // uncomment line below to enable tracing
13 //#define TRACE_ENABLED
14 #endif
15 
16 using System;
17 using System.Collections.Generic;
18 using Antmicro.Renode.Core;
19 using Antmicro.Renode.Peripherals;
20 using Antmicro.Renode.Utilities;
21 using System.Threading;
22 using System.Runtime.CompilerServices;
23 using System.IO;
24 using System.Collections.Concurrent;
25 using Antmicro.Migrant;
26 using Antmicro.Migrant.Hooks;
27 using Antmicro.Renode.Exceptions;
28 using Antmicro.Renode.Utilities.Collections;
29 using System.Diagnostics;
30 using System.Text;
31 using System.Linq;
32 using Antmicro.Renode.Debugging;
33 
34 namespace Antmicro.Renode.Logging
35 {
36     public static class Logger
37     {
AddBackend(ILoggerBackend backend, string name, bool overwrite = false)38         public static void AddBackend(ILoggerBackend backend, string name, bool overwrite = false)
39         {
40             backendNames.AddOrUpdate(name, backend, (key, value) =>
41             {
42                 if(!overwrite)
43                 {
44                     throw new RecoverableException(string.Format("Backend with name '{0}' already exists", key));
45                 }
46                 value.Dispose();
47                 return backend;
48             });
49             levels[new BackendSourceIdPair(backend, -1)] = backend.GetLogLevel();
50             foreach(var level in backend.GetCustomLogLevels())
51             {
52                 levels[new BackendSourceIdPair(backend, level.Key)] = level.Value;
53             }
54             UpdateMinimumLevel();
55             backends.Add(backend);
56         }
57 
RemoveBackend(ILoggerBackend backend)58         public static void RemoveBackend(ILoggerBackend backend)
59         {
60             foreach(var level in levels.Where(pair => pair.Key.backend == backend).ToList())
61             {
62                 levels.TryRemove(level.Key, out var _);
63             }
64             UpdateMinimumLevel();
65             backends.Remove(backend);
66             backend.Dispose();
67         }
68 
GetBackends()69         public static IDictionary<string, ILoggerBackend> GetBackends()
70         {
71             return backendNames;
72         }
73 
Dispose()74         public static void Dispose()
75         {
76             Flush();
77             foreach(var backend in backends.Items)
78             {
79                 backend.Dispose();
80             }
81             backends.Clear();
82             backendNames.Clear();
83         }
84 
SetLogLevel(ILoggerBackend backend, LogLevel level, int sourceId)85         public static void SetLogLevel(ILoggerBackend backend, LogLevel level, int sourceId)
86         {
87             levels[new BackendSourceIdPair(backend, sourceId)] = level;
88             UpdateMinimumLevel();
89         }
90 
Log(LogLevel type, string message, params object[] args)91         public static void Log(LogLevel type, string message, params object[] args)
92         {
93             LogAs(null, type, message, args);
94         }
95 
Log(LogLevel type, string message)96         public static void Log(LogLevel type, string message)
97         {
98             LogAs(null, type, message);
99         }
100 
Log(LogLevel type, string message, object arg1)101         public static void Log(LogLevel type, string message, object arg1)
102         {
103             LogAs(null, type, message, arg1);
104         }
105 
Log(LogLevel type, string message, object arg1, object arg2)106         public static void Log(LogLevel type, string message, object arg1, object arg2)
107         {
108             LogAs(null, type, message, arg1, arg2);
109         }
110 
Log(LogLevel type, string message, object arg1, object arg2, object arg3)111         public static void Log(LogLevel type, string message, object arg1, object arg2, object arg3)
112         {
113             LogAs(null, type, message, arg1, arg2, arg3);
114         }
115 
Error(string message)116         public static void Error(string message)
117         {
118             LogAs(null, LogLevel.Error, message);
119         }
120 
Warning(string message)121         public static void Warning(string message)
122         {
123             LogAs(null, LogLevel.Warning, message);
124         }
125 
Info(string message)126         public static void Info(string message)
127         {
128             LogAs(null, LogLevel.Info, message);
129         }
130 
Debug(string message)131         public static void Debug(string message)
132         {
133             LogAs(null, LogLevel.Debug, message);
134         }
135 
Noisy(string message)136         public static void Noisy(string message)
137         {
138             LogAs(null, LogLevel.Noisy, message);
139         }
140 
ErrorLog(this IEmulationElement e, string message, params object[] args)141         public static void ErrorLog(this IEmulationElement e, string message, params object[] args)
142         {
143             LogAs(e, LogLevel.Error, message, args);
144         }
145 
ErrorLog(this IEmulationElement e, string message)146         public static void ErrorLog(this IEmulationElement e, string message)
147         {
148             LogAs(e, LogLevel.Error, message);
149         }
150 
ErrorLog(this IEmulationElement e, string message, object arg1)151         public static void ErrorLog(this IEmulationElement e, string message, object arg1)
152         {
153             LogAs(e, LogLevel.Error, message, arg1);
154         }
155 
ErrorLog(this IEmulationElement e, string message, object arg1, object arg2)156         public static void ErrorLog(this IEmulationElement e, string message, object arg1, object arg2)
157         {
158             LogAs(e, LogLevel.Error, message, arg1, arg2);
159         }
160 
ErrorLog(this IEmulationElement e, string message, object arg1, object arg2, object arg3)161         public static void ErrorLog(this IEmulationElement e, string message, object arg1, object arg2, object arg3)
162         {
163             LogAs(e, LogLevel.Error, message, arg1, arg2, arg3);
164         }
165 
WarningLog(this IEmulationElement e, string message, params object[] args)166         public static void WarningLog(this IEmulationElement e, string message, params object[] args)
167         {
168             LogAs(e, LogLevel.Warning, message, args);
169         }
170 
WarningLog(this IEmulationElement e, string message)171         public static void WarningLog(this IEmulationElement e, string message)
172         {
173             LogAs(e, LogLevel.Warning, message);
174         }
175 
WarningLog(this IEmulationElement e, string message, object arg1)176         public static void WarningLog(this IEmulationElement e, string message, object arg1)
177         {
178             LogAs(e, LogLevel.Warning, message, arg1);
179         }
180 
WarningLog(this IEmulationElement e, string message, object arg1, object arg2)181         public static void WarningLog(this IEmulationElement e, string message, object arg1, object arg2)
182         {
183             LogAs(e, LogLevel.Warning, message, arg1, arg2);
184         }
185 
WarningLog(this IEmulationElement e, string message, object arg1, object arg2, object arg3)186         public static void WarningLog(this IEmulationElement e, string message, object arg1, object arg2, object arg3)
187         {
188             LogAs(e, LogLevel.Warning, message, arg1, arg2, arg3);
189         }
190 
InfoLog(this IEmulationElement e, string message, params object[] args)191         public static void InfoLog(this IEmulationElement e, string message, params object[] args)
192         {
193             LogAs(e, LogLevel.Info, message, args);
194         }
195 
InfoLog(this IEmulationElement e, string message)196         public static void InfoLog(this IEmulationElement e, string message)
197         {
198             LogAs(e, LogLevel.Info, message);
199         }
200 
InfoLog(this IEmulationElement e, string message, object arg1)201         public static void InfoLog(this IEmulationElement e, string message, object arg1)
202         {
203             LogAs(e, LogLevel.Info, message, arg1);
204         }
205 
InfoLog(this IEmulationElement e, string message, object arg1, object arg2)206         public static void InfoLog(this IEmulationElement e, string message, object arg1, object arg2)
207         {
208             LogAs(e, LogLevel.Info, message, arg1, arg2);
209         }
210 
InfoLog(this IEmulationElement e, string message, object arg1, object arg2, object arg3)211         public static void InfoLog(this IEmulationElement e, string message, object arg1, object arg2, object arg3)
212         {
213             LogAs(e, LogLevel.Info, message, arg1, arg2, arg3);
214         }
215 
DebugLog(this IEmulationElement e, string message, params object[] args)216         public static void DebugLog(this IEmulationElement e, string message, params object[] args)
217         {
218             LogAs(e, LogLevel.Debug, message, args);
219         }
220 
DebugLog(this IEmulationElement e, string message)221         public static void DebugLog(this IEmulationElement e, string message)
222         {
223             LogAs(e, LogLevel.Debug, message);
224         }
225 
DebugLog(this IEmulationElement e, string message, object arg1)226         public static void DebugLog(this IEmulationElement e, string message, object arg1)
227         {
228             LogAs(e, LogLevel.Debug, message, arg1);
229         }
230 
DebugLog(this IEmulationElement e, string message, object arg1, object arg2)231         public static void DebugLog(this IEmulationElement e, string message, object arg1, object arg2)
232         {
233             LogAs(e, LogLevel.Debug, message, arg1, arg2);
234         }
235 
DebugLog(this IEmulationElement e, string message, object arg1, object arg2, object arg3)236         public static void DebugLog(this IEmulationElement e, string message, object arg1, object arg2, object arg3)
237         {
238             LogAs(e, LogLevel.Debug, message, arg1, arg2, arg3);
239         }
240 
NoisyLog(this IEmulationElement e, string message, params object[] args)241         public static void NoisyLog(this IEmulationElement e, string message, params object[] args)
242         {
243             LogAs(e, LogLevel.Noisy, message, args);
244         }
245 
NoisyLog(this IEmulationElement e, string message)246         public static void NoisyLog(this IEmulationElement e, string message)
247         {
248             LogAs(e, LogLevel.Noisy, message);
249         }
250 
NoisyLog(this IEmulationElement e, string message, object arg1)251         public static void NoisyLog(this IEmulationElement e, string message, object arg1)
252         {
253             LogAs(e, LogLevel.Noisy, message, arg1);
254         }
255 
NoisyLog(this IEmulationElement e, string message, object arg1, object arg2)256         public static void NoisyLog(this IEmulationElement e, string message, object arg1, object arg2)
257         {
258             LogAs(e, LogLevel.Noisy, message, arg1, arg2);
259         }
260 
NoisyLog(this IEmulationElement e, string message, object arg1, object arg2, object arg3)261         public static void NoisyLog(this IEmulationElement e, string message, object arg1, object arg2, object arg3)
262         {
263             LogAs(e, LogLevel.Noisy, message, arg1, arg2, arg3);
264         }
265 
Log(this IEmulationElement e, LogLevel type, string message, params object[] args)266         public static void Log(this IEmulationElement e, LogLevel type, string message, params object[] args)
267         {
268             LogAs(e, type, message, args);
269         }
270 
Log(this IEmulationElement e, LogLevel type, string message)271         public static void Log(this IEmulationElement e, LogLevel type, string message)
272         {
273             LogAs(e, type, message);
274         }
275 
Log(this IEmulationElement e, LogLevel type, string message, object arg1)276         public static void Log(this IEmulationElement e, LogLevel type, string message, object arg1)
277         {
278             LogAs(e, type, message, arg1);
279         }
280 
Log(this IEmulationElement e, LogLevel type, string message, object arg1, object arg2)281         public static void Log(this IEmulationElement e, LogLevel type, string message, object arg1, object arg2)
282         {
283             LogAs(e, type, message, arg1, arg2);
284         }
285 
Log(this IEmulationElement e, LogLevel type, string message, object arg1, object arg2, object arg3)286         public static void Log(this IEmulationElement e, LogLevel type, string message, object arg1, object arg2, object arg3)
287         {
288             LogAs(e, type, message, arg1, arg2, arg3);
289         }
290 
LogAs(object o, LogLevel type, string message, params object[] args)291         public static void LogAs(object o, LogLevel type, string message, params object[] args)
292         {
293             // The inner log method is only skipped if the level of this message is lower than the level set
294             // for any source on any backend. This means that setting any element's log level to Debug will
295             // make all Debug and higher logs get sent to the backends.
296             if(type < minLevel)
297             {
298                 return;
299             }
300             var emulationManager = EmulationManager.Instance;
301             if(emulationManager != null)
302             {
303                 ((ActualLogger)emulationManager.CurrentEmulation.CurrentLogger).ObjectInnerLog(o, type, message, args);
304             }
305         }
306 
LogAs(object o, LogLevel type, string message)307         public static void LogAs(object o, LogLevel type, string message)
308         {
309             if(type < minLevel)
310             {
311                 return;
312             }
313             var emulationManager = EmulationManager.Instance;
314             if(emulationManager != null)
315             {
316                 ((ActualLogger)emulationManager.CurrentEmulation.CurrentLogger).ObjectInnerLog(o, type, message);
317             }
318         }
319 
LogAs(object o, LogLevel type, string message, object arg1)320         public static void LogAs(object o, LogLevel type, string message, object arg1)
321         {
322             if(type < minLevel)
323             {
324                 return;
325             }
326             var emulationManager = EmulationManager.Instance;
327             if(emulationManager != null)
328             {
329                 ((ActualLogger)emulationManager.CurrentEmulation.CurrentLogger).ObjectInnerLog(o, type, message, arg1);
330             }
331         }
332 
LogAs(object o, LogLevel type, string message, object arg1, object arg2)333         public static void LogAs(object o, LogLevel type, string message, object arg1, object arg2)
334         {
335             if(type < minLevel)
336             {
337                 return;
338             }
339             var emulationManager = EmulationManager.Instance;
340             if(emulationManager != null)
341             {
342                 ((ActualLogger)emulationManager.CurrentEmulation.CurrentLogger).ObjectInnerLog(o, type, message, arg1, arg2);
343             }
344         }
345 
LogAs(object o, LogLevel type, string message, object arg1, object arg2, object arg3)346         public static void LogAs(object o, LogLevel type, string message, object arg1, object arg2, object arg3)
347         {
348             if(type < minLevel)
349             {
350                 return;
351             }
352             var emulationManager = EmulationManager.Instance;
353             if(emulationManager != null)
354             {
355                 ((ActualLogger)emulationManager.CurrentEmulation.CurrentLogger).ObjectInnerLog(o, type, message, arg1, arg2, arg3);
356             }
357         }
358 
Flush()359         public static void Flush()
360         {
361             var emulationManager = EmulationManager.Instance;
362             if(emulationManager != null)
363             {
364                 ((ActualLogger)emulationManager.CurrentEmulation.CurrentLogger).Flush();
365             }
366         }
367 
368 // see a comment at the top
369 #if !TRACE_ENABLED
370         [Conditional("TRACE_ENABLED")]
371 #endif
Trace(this object o, LogLevel type, string message = null, [CallerLineNumber] int lineNumber = 0, [CallerMemberName] string caller = null, [CallerFilePath] string fileName = null)372         public static void Trace(this object o, LogLevel type, string message = null,
373             [CallerLineNumber] int lineNumber = 0,
374             [CallerMemberName] string caller = null,
375             [CallerFilePath] string fileName = null)
376         {
377             var fullMessage = new StringBuilder($"[TRACE][t:{Thread.CurrentThread.Name}/{Thread.CurrentThread.ManagedThreadId}]");
378 #if DEBUG
379             if(o is IIdentifiable identifiable)
380             {
381                 fullMessage.Append($"[s:{identifiable.GetDescription()}]");
382             }
383 #endif
384             fullMessage.Append($" {message} in {caller} ({Path.GetFileName(fileName)}:{lineNumber})");
385 
386             LogAs(o, type, fullMessage.ToString());
387         }
388 
389 // see a comment at the top
390 #if !TRACE_ENABLED
391         [Conditional("TRACE_ENABLED")]
392 #endif
Trace(this object o, string message = null, [CallerLineNumber] int lineNumber = 0, [CallerMemberName] string caller = null, [CallerFilePath] string fileName = null)393         public static void Trace(this object o, string message = null,
394             [CallerLineNumber] int lineNumber = 0,
395             [CallerMemberName] string caller = null,
396             [CallerFilePath] string fileName = null)
397         {
398             Trace(o, LogLevel.Info, message, lineNumber, caller, fileName);
399         }
400 
TraceRegion(this object o, string message = null, [CallerLineNumber] int lineNumber = 0, [CallerMemberName] string caller = null, [CallerFilePath] string fileName = null)401         public static IDisposable TraceRegion(this object o, string message = null,
402             [CallerLineNumber] int lineNumber = 0,
403             [CallerMemberName] string caller = null,
404             [CallerFilePath] string fileName = null)
405         {
406             var result = new DisposableWrapper();
407             Trace(o, $"Entering: {message}", lineNumber, caller, fileName);
408             result.RegisterDisposeAction(() => Trace(o, $"Leaving: {message}. Entered", lineNumber, caller, fileName));
409             return result;
410         }
411 
LogUnhandledRead(this IPeripheral peripheral, long offset)412         public static void LogUnhandledRead(this IPeripheral peripheral, long offset)
413         {
414             peripheral.Log(LogLevel.Warning, "Unhandled read from offset 0x{0:X}.", offset);
415         }
416 
LogUnhandledWrite(this IPeripheral peripheral, long offset, ulong value)417         public static void LogUnhandledWrite(this IPeripheral peripheral, long offset, ulong value)
418         {
419             peripheral.Log(LogLevel.Warning, "Unhandled write to offset 0x{0:X}, value 0x{1:X}.", offset, value);
420         }
421 
422         public static bool PrintFullName { get; set; }
423 
424         public static readonly LogLevel DefaultLogLevel = LogLevel.Info;
425 
GetLogger()426         internal static ILogger GetLogger()
427         {
428             var logger = new ActualLogger();
429             foreach(var backend in backends.Items)
430             {
431                 backend.Reset();
432             }
433             return logger;
434         }
435 
436         private static ulong nextEntryId = 0;
437         private static LogLevel minLevel = DefaultLogLevel;
438         private static readonly ConcurrentDictionary<string, ILoggerBackend> backendNames = new ConcurrentDictionary<string, ILoggerBackend>();
439         private static readonly FastReadConcurrentCollection<ILoggerBackend> backends = new FastReadConcurrentCollection<ILoggerBackend>();
440         private static readonly ConcurrentDictionary<BackendSourceIdPair, LogLevel> levels = new ConcurrentDictionary<BackendSourceIdPair, LogLevel>();
441 
442 
GetGenericName(object o)443         private static string GetGenericName(object o)
444         {
445             if(Misc.IsPythonObject(o))
446             {
447                 return Misc.GetPythonName(o);
448             }
449             var type = o.GetType();
450             return PrintFullName ? type.FullName : type.Name;
451         }
452 
UpdateMinimumLevel()453         private static void UpdateMinimumLevel()
454         {
455             minLevel = levels.Min(l => l.Value);
456         }
457 
458         internal class ActualLogger : ILogger
459         {
ActualLogger()460             public ActualLogger()
461             {
462                 Init();
463             }
464 
Dispose()465             public void Dispose()
466             {
467                 if(!SynchronousLogging)
468                 {
469                     StopLoggingThread();
470                 }
471             }
472 
GetMachineName(int id)473             public string GetMachineName(int id)
474             {
475                 string objectName;
476                 string machineName;
477                 if(TryGetName(id, out objectName, out machineName))
478                 {
479                     return machineName;
480                 }
481                 return null;
482             }
483 
GetObjectName(int id)484             public string GetObjectName(int id)
485             {
486                 string objectName;
487                 string machineName;
488                 if(TryGetName(id, out objectName, out machineName))
489                 {
490                     return objectName;
491                 }
492                 return null;
493             }
494 
TryGetName(int id, out string objectName, out string machineName)495             public bool TryGetName(int id, out string objectName, out string machineName)
496             {
497                 object obj;
498                 if(logSourcesMap.TryGetObject(id, out obj))
499                 {
500                     if(EmulationManager.Instance.CurrentEmulation.TryGetEmulationElementName(obj, out objectName, out machineName))
501                     {
502                         return true;
503                     }
504                 }
505 
506                 objectName = null;
507                 machineName = null;
508                 return false;
509             }
510 
GetOrCreateSourceId(object source)511             public int GetOrCreateSourceId(object source)
512             {
513                 return logSourcesMap.GetOrCreateId(source, () => Interlocked.Increment(ref nextNameId));
514             }
515 
TryGetSourceId(object source, out int id)516             public bool TryGetSourceId(object source, out int id)
517             {
518                 return logSourcesMap.TryGetId(source, out id);
519             }
520 
ObjectInnerLog(object o, LogLevel type, string message, params object[] args)521             public void ObjectInnerLog(object o, LogLevel type, string message, params object[] args)
522             {
523                 int sourceId = (o == null) ? -1 : GetOrCreateSourceId(o);
524                 if(args?.Length > 0)
525                 {
526                     message = string.Format(message, args);
527                 }
528 
529                 var entry = new LogEntry(CustomDateTime.Now, type, message, sourceId, alwaysAppendMachineName, Thread.CurrentThread.ManagedThreadId);
530 
531                 if(SynchronousLogging)
532                 {
533                     lock(innerLock)
534                     {
535                         entry.Id = Logger.nextEntryId++;
536                         WriteLogEntryToBackends(entry);
537                     }
538                 }
539                 else
540                 {
541                     entries.Add(entry);
542                 }
543             }
544 
Flush()545             public void Flush()
546             {
547                 if(SynchronousLogging)
548                 {
549                     FlushBackends();
550                     return;
551                 }
552 
553                 StopLoggingThread();
554 
555                 if(aggregateLogs)
556                 {
557                     FlushAggregatedLogs();
558                 }
559 
560                 // switch collections to avoid
561                 // stucking forever in the loop below
562                 var localEntries = entries;
563                 entries = new BlockingCollection<LogEntry>(10000);
564 
565                 while(localEntries.TryTake(out var entry))
566                 {
567                     // we set ids here to avoid the need of locking counter in `ObjectInnerLog`
568                     entry.Id = Logger.nextEntryId++;
569                     WriteLogEntryToBackends(entry);
570                 }
571 
572                 FlushBackends();
573                 StartLoggingThread();
574             }
575 
576             public bool SynchronousLogging
577             {
578                 get => useSynchronousLogging;
579                 set
580                 {
581                     if(useSynchronousLogging == value)
582                     {
583                         return;
584                     }
585 
586                     useSynchronousLogging = value;
587                     if(value)
588                     {
589                         StopLoggingThread();
590                     }
591                     else
592                     {
593                         StartLoggingThread();
594                     }
595                 }
596             }
597 
LoggingThreadBody()598             private void LoggingThreadBody()
599             {
600                 while(!stopThread)
601                 {
602                     LogEntry entry;
603                     try
604                     {
605                         entry = entries.Take(cancellationToken.Token);
606                     }
607                     catch(OperationCanceledException)
608                     {
609                         break;
610                     }
611 
612                     if(!aggregateLogs)
613                     {
614                         // we set ids here to avoid the need of locking counter in `ObjectInnerLog`
615                         entry.Id = Logger.nextEntryId++;
616                         WriteLogEntryToBackends(entry);
617                         continue;
618                     }
619 
620                     lock(aggregationFlushLock)
621                     {
622                         if(entry.EqualsWithoutIdTimeAndCount(lastLoggedEntry))
623                         {
624                             repeatLogEntryCount++;
625                             if(repeatLogEntryCount >= MaxRepeatedLogs)
626                             {
627                                 FlushAggregatedLogs();
628                             }
629                         }
630                         else
631                         {
632                             FlushAggregatedLogs();
633 
634                             // we set ids here to avoid the need of locking counter in `ObjectInnerLog`
635                             entry.Id = Logger.nextEntryId++;
636                             WriteLogEntryToBackends(entry);
637                             lastLoggedEntry = entry;
638                         }
639                     }
640                 }
641             }
642 
WriteLogEntryToBackends(LogEntry entry)643             private void WriteLogEntryToBackends(LogEntry entry)
644             {
645                 var allBackends = Logger.backends.Items;
646                 for(var i = 0; i < allBackends.Length; i++)
647                 {
648                     allBackends[i].Log(entry);
649                 }
650             }
651 
FlushBackends()652             private void FlushBackends()
653             {
654                 var allBackends = Logger.backends.Items;
655                 for(var i = 0; i < allBackends.Length; i++)
656                 {
657                     allBackends[i].Flush();
658                 }
659             }
660 
FlushAggregatedLogs()661             private void FlushAggregatedLogs()
662             {
663                 if(repeatLogEntryCount == 0)
664                 {
665                     return;
666                 }
667 
668                 lastLoggedEntry.Count = repeatLogEntryCount;
669 
670                 WriteLogEntryToBackends(lastLoggedEntry);
671 
672                 repeatLogEntryCount = 0;
673 
674                 // reset timer
675                 logAggregatorTimer?.Change(MaxAggregateTimeMs, MaxAggregateTimeMs);
676             }
677 
678             [PostDeserialization]
Init()679             private void Init()
680             {
681                 logSourcesMap = new LogSourcesMap();
682                 nextNameId = 0;
683 
684                 innerLock = new object();
685                 aggregationFlushLock = new object();
686 
687                 SynchronousLogging = ConfigurationManager.Instance.Get("general", "use-synchronous-logging", false);
688                 alwaysAppendMachineName = ConfigurationManager.Instance.Get("general", "always-log-machine-name", false);
689                 aggregateLogs = ConfigurationManager.Instance.Get("general", "collapse-repeated-log-entries", true);
690 
691                 if(!SynchronousLogging)
692                 {
693                     entries = new BlockingCollection<LogEntry>(10000);
694 
695                     StartLoggingThread();
696                 }
697             }
698 
StartLoggingThread()699             private void StartLoggingThread()
700             {
701                 lock(innerLock)
702                 {
703                     if(aggregateLogs)
704                     {
705                         logAggregatorTimer = new Timer(x =>
706                         {
707                             lock(aggregationFlushLock)
708                             {
709                                 FlushAggregatedLogs();
710                             }
711                         }, null, MaxAggregateTimeMs, MaxAggregateTimeMs);
712                     }
713 
714                     cancellationToken = new CancellationTokenSource();
715                     loggingThread = new Thread(LoggingThreadBody);
716                     loggingThread.IsBackground = true;
717                     loggingThread.Name = "Logging thread";
718                     loggingThread.Start();
719                 }
720             }
721 
StopLoggingThread()722             private void StopLoggingThread()
723             {
724                 lock(innerLock)
725                 {
726                     if(loggingThread == null)
727                     {
728                         return;
729                     }
730 
731                     stopThread = true;
732                     cancellationToken.Cancel();
733                     loggingThread.Join();
734                     loggingThread = null;
735 
736                     logAggregatorTimer?.Dispose();
737                     logAggregatorTimer = null;
738                 }
739             }
740 
741             [Transient]
742             private int repeatLogEntryCount;
743             [Transient]
744             private LogEntry lastLoggedEntry;
745             [Transient]
746             private Timer logAggregatorTimer;
747             [Transient]
748             private bool aggregateLogs;
749 
750             [Transient]
751             private bool alwaysAppendMachineName;
752 
753             [Transient]
754             private bool useSynchronousLogging;
755 
756             [Transient]
757             private object innerLock;
758 
759             [Transient]
760             private object aggregationFlushLock;
761 
762             [Transient]
763             private Thread loggingThread;
764 
765             [Transient]
766             private CancellationTokenSource cancellationToken;
767 
768             [Transient]
769             private volatile bool stopThread = false;
770 
771             [Transient]
772             private BlockingCollection<LogEntry> entries;
773             [Transient]
774             private int nextNameId;
775             [Transient]
776             private LogSourcesMap logSourcesMap;
777 
778             private const int MaxRepeatedLogs = 10000;
779             private const int MaxAggregateTimeMs = 500;
780 
781             private class LogSourcesMap
782             {
LogSourcesMap()783                 public LogSourcesMap()
784                 {
785                     objectToIdMap = new ConcurrentDictionary<WeakWrapper<object>, int>();
786                     idToObjectMap = new ConcurrentDictionary<int, WeakWrapper<object>>();
787                 }
788 
GetOrCreateId(object o, Func<int> idProvider)789                 public int GetOrCreateId(object o, Func<int> idProvider)
790                 {
791                     return objectToIdMap.GetOrAdd(WeakWrapper<object>.CreateForComparison(o), s =>
792                     {
793                         s.ConvertToRealWeakWrapper();
794 
795                         var id = idProvider();
796                         idToObjectMap.TryAdd(id, s);
797                         return id;
798                     });
799                 }
800 
TryGetId(object o, out int sourceId)801                 public bool TryGetId(object o, out int sourceId)
802                 {
803                     return objectToIdMap.TryGetValue(WeakWrapper<object>.CreateForComparison(o), out sourceId);
804                 }
805 
TryGetObject(int id, out object obj)806                 public bool TryGetObject(int id, out object obj)
807                 {
808                     WeakWrapper<object> outResult;
809                     var result = idToObjectMap.TryGetValue(id, out outResult);
810                     if(result)
811                     {
812                         return outResult.TryGetTarget(out obj);
813                     }
814 
815                     obj = null;
816                     return false;
817                 }
818 
819                 private readonly ConcurrentDictionary<int, WeakWrapper<object>> idToObjectMap;
820                 private readonly ConcurrentDictionary<WeakWrapper<object>, int> objectToIdMap;
821             }
822         }
823 
824         internal struct BackendSourceIdPair
825         {
BackendSourceIdPairAntmicro.Renode.Logging.Logger.BackendSourceIdPair826             public BackendSourceIdPair(ILoggerBackend backend, int sourceId)
827             {
828                 this.backend = backend;
829                 this.sourceId = sourceId;
830             }
831 
832             public readonly ILoggerBackend backend;
833             public readonly int sourceId;
834         }
835     }
836 }
837