Giter Site home page Giter Site logo

Comments (13)

roubachof avatar roubachof commented on July 30, 2024 1

lol didn't see it was my blog post referenced in this thread.

from metrolog.

sdiaman1 avatar sdiaman1 commented on July 30, 2024 1

@EmilAlipiev, yeah, we tested the GetCompressedLogs-deadlock workaround on Android, iOS, and Windows (UWP). It seems to be working fine on all of these platforms.

But it would be nice to have a permanent fix for this; #118 may help, but may also be incomplete. (Note: We only tested this PR's changes on Windows, so there may be additional changes that are needed to get this to work on Android and iOS, and also maybe for other scenarios that we didn't test for on Windows.)

Also, since you mentioned iOS, on Android and iOS, GetCompressedLogs will throw an exception if the log folder doesn't exist. We ensure that a message is logged (see the Configure method, in the code below) before we call GetCompressedLogs, so that the log folder exists. (Note: MetroLog.Shared.WinRT\FileTarget.cs, GetCompressedLogsInternal(), used by Windows, calls EnsureInitializedAsync(), but MetroLog.NetFx\FileTarget.cs, used by Android and iOS, doesn't.)

Just in case it helps, the following is our LogManager class. (Note: I modified it before posting it here; hopefully it works okay.).

If you're running into other issues, let us know, we may be able to help.

LogManager.cs:

using MetroLog;
using MetroLog.Targets;
using System;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using System.Threading.Tasks;

namespace TestApp
{
  /// <summary>
  /// <a href="https://github.com/novotnyllc/MetroLog">MetroLog</a> log manager.
  /// </summary>
  public class LogManager
  {
    /// <summary>
    /// True if the app has configured logging, otherwise false.
    /// </summary>
    public static bool Configured { get; set; } = false;

    /// <summary>
    /// The file target.
    /// </summary>
    private static StreamingFileTarget streamingFileTarget = null;

    /// <summary>
    /// Gets a logger instance for the specified <paramref name="type"/>.
    /// </summary>
    public static ILogger GetLogger(Type type)
    {
      return LogManagerFactory.DefaultLogManager.GetLogger(type);
    }

    /// <summary>
    /// Gets a logger instance for the specified <paramref name="name"/>.
    /// </summary>
    public static ILogger GetLogger(string name)
    {
      return LogManagerFactory.DefaultLogManager.GetLogger(name);
    }

    /// <summary>
    /// Configures the logging to log to files (and the debug output window, if debugging).
    /// Or reconfigures it; this method may be called as many times as desired,
    /// to update the min logging level and number of days to retain the log files.
    /// </summary>
    /// <remarks>
    /// By default, the log files are named like 'Log - YYYYMMDD.log', and there's one file per day.
    /// MetroLog.Shared\Targets\FileTargetBase.cs, CheckCleanupAsync(), deletes the old log file(s).
    /// It's called each time that a new message is logged, but only checks-and-cleans-up once per hour.
    /// It uses the RetainDays and a file's *created* date to determine if it should delete the file.
    /// </remarks>
    /// <param name="minLevel">The min logging level (e.g., Warn).</param>
    /// <param name="retainDays">The number of days to retain the log files (e.g., 3).</param>
    /// <references>
    /// <a href="https://github.com/novotnyllc/MetroLog/wiki/Configuration#adding-your-own-targets">Configuration: Adding your own targets</a>
    /// <a href="https://stackoverflow.com/a/25433196">How do I get MetroLog to start logging...?</a>
    /// </references>
    public static void Configure(LogLevel minLevel, int retainDays)
    {
      if (!Configured)
      {
        LogManagerFactory.DefaultConfiguration = new LoggingConfiguration();

#if DEBUG
        // Show the log messages in the debug output window.
        LogManagerFactory.DefaultConfiguration.AddTarget(LogLevel.Trace, LogLevel.Fatal, new DebugTarget());
#endif

        streamingFileTarget = new StreamingFileTarget { RetainDays = retainDays };
        LogManagerFactory.DefaultConfiguration.AddTarget(minLevel, LogLevel.Fatal, streamingFileTarget);

        LogManagerFactory.DefaultConfiguration.IsEnabled = true;

        Configured = true;
      }
      else
      {
        SetMinLevel(minLevel);
        streamingFileTarget.RetainDays = retainDays;
      }

      // The following message is logged so that it's known if/when the log settings change.
      // But also it ensures that the log folder exists.
      // On Android (and iOS?) GetCompressedLogs() throws an exception if it doesn't.
      ILogger log = GetLogger(nameof(LogManager));
      log.Info($"Log level: {minLevel}, retain days: {retainDays}");
    }

    /// <summary>
    /// Changes the <see cref="streamingFileTarget"/>'s min logging level.
    /// </summary>
    /// <remarks>
    /// The DebugTarget's MinLevel isn't changed, it remains set to Trace, if debugging.
    /// </remarks>
    /// <references>
    /// <a href="https://github.com/novotnyllc/MetroLog/issues/45#issuecomment-44498569">Change LogLevel at runtime</a>
    /// <a href="https://stackoverflow.com/a/40917899">Changing read only properties with reflection</a>
    /// </references>
    private static void SetMinLevel(LogLevel minlevel)
    {
      FieldInfo field = typeof(LoggingConfiguration).GetField("bindings", BindingFlags.Instance | BindingFlags.NonPublic);
      IEnumerable<object> bindings = field.GetValue(LogManagerFactory.DefaultConfiguration) as IEnumerable<object>;

      foreach (var binding in bindings)
      {
        // ONLY change the StreamingFileTarget's MinLevel. Keep the DebugTarget's MinLevel set to Trace.
        field = binding.GetType().GetField("<Target>k__BackingField", BindingFlags.Instance | BindingFlags.NonPublic);
        var target = field.GetValue(binding);
        if (target.GetType() != typeof(StreamingFileTarget)) continue;

        field = binding.GetType().GetField("<MinLevel>k__BackingField", BindingFlags.Instance | BindingFlags.NonPublic);
        field.SetValue(binding, minlevel);
      }
    }

    /// <summary>
    /// Gets the compressed logs.
    /// </summary>
    /// <remarks>
    /// GetCompressedLogs() deadlocks on Windows and Android (and iOS?) if a message is logged and then this method is called,
    /// so this method uses CloseAllOpenStreamsInternal() and GetCompressedLogsInternal() as a temporary workaround.
    /// More info: <a href="https://github.com/novotnyllc/MetroLog/issues/117#issuecomment-1126405102">GetCompressedLogs - how to for xamarin.forms project?</a>
    /// </remarks>
    /// <references>
    /// <a href="https://devblogs.microsoft.com/appcenter/give-your-crashes-a-context-through-logs-attachments/#the-magic">Give Your Crashes a Context through Logs Attachments</a>
    /// </references>
    public static MemoryStream GetCompressedLogs()
    {
      Stream compressedLogs = null;

      Task.Run(async () =>
      {
        try
        {
          MethodInfo method = typeof(FileTargetBase).GetMethod("CloseAllOpenStreamsInternal", BindingFlags.Instance | BindingFlags.NonPublic);
          method.Invoke(streamingFileTarget, null);

          method = typeof(FileTarget).GetMethod("GetCompressedLogsInternal", BindingFlags.Instance | BindingFlags.NonPublic);
          Task<Stream> task = method.Invoke(streamingFileTarget, null) as Task<Stream>;
          compressedLogs = await task.ConfigureAwait(false);
        }
        catch (Exception ex)
        {
          ILogger log = GetLogger(nameof(LogManager));
          log.Error(nameof(GetCompressedLogs), ex);
        }
      }).Wait();

      return compressedLogs as MemoryStream;
    }
  }
}

The above code is used like:

// Configure the logging.
LogManager.Configure(LogLevel.Trace, 3);

// Log a message...where MainPage is the class name.
ILogger log = LogManager.GetLogger(nameof(MainPage));
log.Info("This is an info message");

// Get the compressed logs.
using (MemoryStream compressedLogs = LogManager.GetCompressedLogs())
{
  // Do something with the compressedLogs MemoryStream...
}

from metrolog.

EmilAlipiev avatar EmilAlipiev commented on July 30, 2024

It looks like that in the article something is missing. he didnt mention how to get LogManager. My code below isnt working because i am not sure if it is correct to use StreamingFileTarget as i am getting exception regarding it.

02-23 17:43:03.300 I/MonoDroid(29136): UNHANDLED EXCEPTION:
02-23 17:43:03.319 I/MonoDroid(29136): System.AggregateException: One or more errors occurred. (Object reference not set to an instance of an object.) ---> System.NullReferenceException: Object reference not set to an instance of an object.
02-23 17:43:03.319 I/MonoDroid(29136):   at MetroLog.FileTarget.GetCompressedLogsInternal () [0x0000f] in C:\projects\metrolog\MetroLog.NetFx\FileTarget.cs:39 
02-23 17:43:03.319 I/MonoDroid(29136):   at MetroLog.Targets.FileTargetBase.GetCompressedLogs () [0x0009d] in C:\projects\metrolog\MetroLog.Shared\Targets\FileTargetBase.cs:64 

from metrolog.

sdiaman1 avatar sdiaman1 commented on July 30, 2024

I think I'm trying to do the same (or a similar?) thing...

I have the following method that gets the compressed logs (in my LogManager class, in a .NET Standard library):

public static MemoryStream GetCompressedLogs()
{
  Stream compressedLogs = null;
  Task.Run(async () => compressedLogs = await LogManagerFactory.DefaultLogManager.GetCompressedLogs()).Wait();
  return compressedLogs as MemoryStream;
}

And the following code that calls it (in my Xamarin mobile app):

Crashes.GetErrorAttachments = report =>
{
  using (MemoryStream compressedLogs = LogManager.GetCompressedLogs())
  {
    return new[] { ErrorAttachmentLog.AttachmentWithBinary(compressedLogs.ToArray(), "logs.zip", "application/zip") };
  }
};

The issue I'm having is if I try to log a message first, and then get the compressed logs, it deadlocks.

See minimal, reproducible example in pull request #118.

I tried most (all?) of the potential solutions at https://stackoverflow.com/questions/9343594/how-to-call-asynchronous-method-from-synchronous-method-in-c, but nothing worked for me. Except for adding ConfigureAwait(false) to a couple of places in this library....The changes in pull request #118.

I'm thinking of maybe using the following code as a temporary workaround:

public static MemoryStream GetCompressedLogs()
{
  Stream compressedLogs = null;
  Task.Run(async () =>
  {
    try
    {
      MethodInfo method = typeof(FileTargetBase).GetMethod("CloseAllOpenStreamsInternal", BindingFlags.Instance | BindingFlags.NonPublic);
      method.Invoke(streamingFileTarget, null);

      method = typeof(FileTarget).GetMethod("GetCompressedLogsInternal", BindingFlags.Instance | BindingFlags.NonPublic);
      Task<Stream> task = method.Invoke(streamingFileTarget, null) as Task<Stream>;
      compressedLogs = await task.ConfigureAwait(false);
    }
    catch (Exception ex)
    {
      // Log it...
    }
  }).Wait();
  return compressedLogs as MemoryStream;
}

(Where streamingFileTarget is a class member, set to new StreamingFileTarget() when the logging is configured.)

It calls CloseAllOpenStreamsInternal() and GetCompressedLogsInternal(), same as GetCompressedLogs() but without the locking. I'm hoping that this will work well enough until a fix is implemented to this library.

from metrolog.

roubachof avatar roubachof commented on July 30, 2024

I'm using metrolog to attach logs to AppCenter, this is what I am doing (I'm using Prism.Dryioc):

in App.xaml.cs:

```csharp
using LogLevel = MetroLog.LogLevel;

namespace Whatever
{
    public partial class App
    {
        private static ILogger log;

        public App(IPlatformInitializer initializer)
            : base(initializer)
        {
        }

        protected override async void OnInitialized()
        {
            InitializeComponent();

            var result = await NavigationService.NavigateAsync("NavigationPage/MainPage");
            if (result.Exception != null)
            {
                throw result.Exception;
            }
        }

        protected override void RegisterTypes(IContainerRegistry containerRegistry)
        {
            RegisterInfrastructure(containerRegistry);

            RegisterDomain(containerRegistry);

            RegisterNavigation(containerRegistry);
        }

        private static void RegisterInfrastructure(IContainerRegistry containerRegistry)
        {
            InitializeAppCenter();

            log = InitializeLogger();

            containerRegistry.RegisterInstance<Prism.Logging.ILogger>(new PrismLoggerWrapper(log));
            containerRegistry.RegisterSingleton<IAppInfo, AppInfoImplementation>();
        }

        private static void RegisterDomain(IContainerRegistry containerRegistry)
        {
            containerRegistry.RegisterSingleton<ILightService, LightServiceMock>();
        }

        private static void RegisterNavigation(IContainerRegistry containerRegistry)
        {
            containerRegistry.RegisterForNavigation<NavigationPage>();

            containerRegistry.RegisterForNavigation<MainPage, MainPageViewModel>();
            containerRegistry.RegisterForNavigation<LightEditPage, LightEditPageViewModel>();
        }

        private static void InitializeAppCenter()
        {
#if RELEASE
            if (PlatformService.IsEmulator)
            {
                return;
            }

            Crashes.GetErrorAttachments = report =>
            {
                var compressedLogs = LoggerFactory.GetCompressedLogs();

                return new[]
                {
                    ErrorAttachmentLog.AttachmentWithBinary(
                        compressedLogs.ToArray(),
                        "logs.zip",
                        "application/x-zip-compressed"),
                };
            };

            AppCenter.Start(
                "ios=XXXXX;android=XXXXX",
                typeof(Analytics),
                typeof(Crashes),
                typeof(Distribute));
#endif
        }

        private static ILogger InitializeLogger()
        {
            var config = new LoggingConfiguration();

#if RELEASE
            // Will be stored in: $"MetroLog{Path.DirectorySeparatorChar}MetroLogs{Path.DirectorySeparatorChar}Log.log"
            if (!PlatformService.IsEmulator)
            {
                config.AddTarget(LogLevel.Info, LogLevel.Fatal, new StreamingFileTarget { RetainDays = 2 });
            }
            else
            {
                config.AddTarget(LogLevel.Debug, LogLevel.Fatal, new DebugTarget());
            }
#else
            config.AddTarget(LogLevel.Debug, LogLevel.Fatal, new DebugTarget());
#endif

            LoggerFactory.Initialize(config);

            var logger = LoggerFactory.GetLogger(nameof(App));

            TaskMonitorConfiguration.ErrorHandler = (t, m, e) => logger.Error(m, e);

            return logger;
        }
    }
}

LoggerFactory.cs

using System;
using System.IO;
using System.Threading.Tasks;

using MetroLog;

namespace Whatever.Infrastructure.Logging
{
    public static class LoggerFactory
    {
        private static ILogManager logManager;

        public static void Initialize(LoggingConfiguration configuration)
        {
            logManager = LogManagerFactory.CreateLogManager(configuration);
        }

        public static ILogger GetLogger(string loggerName)
        {
            if (logManager == null)
            {
                throw new InvalidOperationException("LogFactory must be Initialized before creating any logger");
            }

            return logManager.GetLogger(loggerName);
        }

        public static ILogger TryGetLogger(string loggerName)
        {
            if (logManager == null)
            {
                return new ConsoleLogger();
            }

            return logManager.GetLogger(loggerName);
        }

        public static MemoryStream GetCompressedLogs()
        {
            Stream compressedLogs = null;
            Task.Run(async () => compressedLogs = await logManager.GetCompressedLogs()).Wait();

            return (MemoryStream)compressedLogs;
        }
    }
}

from metrolog.

sdiaman1 avatar sdiaman1 commented on July 30, 2024

@roubachof, yeah, it was your article that started us down this road :) many thanks!!

MetroLog (and that GetCompressedLogs-deadlock workaround that I mentioned) seems to be working quite well, so far, fingers crossed. Hopefully, this will help us to get better info about why our app is crashing.

from metrolog.

EmilAlipiev avatar EmilAlipiev commented on July 30, 2024

I have to revisit this 2 years already since I posted this and I couldn't achieve it before. So you are saying that you solved the deadlock problem and it is working fine for you? Have you had chance to test for ios also?

from metrolog.

roubachof avatar roubachof commented on July 30, 2024

@EmilAlipiev you can use the LoggerFactory I attached. I use them in a lot of apps and never had any deadlock issues

from metrolog.

sdiaman1 avatar sdiaman1 commented on July 30, 2024

@roubachof, did you try logging a message before you get the compressed logs? See minimal, reproducible example in pull request #118.

from metrolog.

roubachof avatar roubachof commented on July 30, 2024

@sdiaman1 yes of course, I use the LoggerFactory in multiple apps that compressed logs on crash and sen them to appcenter

from metrolog.

sdiaman1 avatar sdiaman1 commented on July 30, 2024

@roubachof, did you try the minimal, reproducible example in pull request #118, does it not deadlock?

For example, if you're only getting the compressed logs for App Center crash reports, and you're not logging any messages immediately before that (at app startup), then you might never run into this issue.

from metrolog.

roubachof avatar roubachof commented on July 30, 2024

Nope cause I'm using my LoggerFactory to get thé compressed logs

from metrolog.

roubachof avatar roubachof commented on July 30, 2024

you will be able to use the MetroLog.Maui package when you will migrate to maui :)
https://github.com/roubachof/MetroLog

from metrolog.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.