CvsRequestLogger - cannot access a closed stream

For some time now I’ve been getting the exception below in my log files. I have tried updating to later versions of service stack - with no change. The cvs log is being created, but I’m concerned I may be missing some output given the exception.

System.ObjectDisposedException: Cannot access a closed Stream.
at System.IO.__Error.StreamIsClosed()
at System.IO.MemoryStream.get_Capacity()
at _GetProperty[T]Capacity(MemoryStream )
at ServiceStack.Text.Common.WriteType2.WriteProperties(TextWriter writer, Object instance) at ServiceStack.Text.TypeSerializer.SerializeToString(Object value, Type type) at ServiceStack.Text.TypeSerializer.SerializeToString[T](T value) at ServiceStack.Text.CsvWriter1.Write(TextWriter writer, IEnumerable1 records) at ServiceStack.Text.CsvSerializer1.WriteObject(TextWriter writer, Object value)
at ServiceStack.Text.CsvSerializer.SerializeToString[T](T value)
at ServiceStack.CsvRequestLogger.WriteLogs(List`1 logs, String logFile) (ServiceStack.CsvRequestLogger)

Running ServiceStack 5.8.0 on Windows Server 2016 on .Net 4.6.1

I configure CvsRequestLogger using:
Plugins.Add(
new RequestLogsFeature()
{
RequestLogger = new CsvRequestLogger()
});

The CSV Serializer writes to a StringWriter/StringBuilder not a MemoryStream and given the Exception is during WriteProperties it suggests the Exception occurs because it’s trying to serialize a non-serializable object which contains a MemoryStream property.

From the RequestLogEntry.cs the only properties that could store an object with a MemoryStream property are:

public object RequestDto { get; set; }
public object Session { get; set; }
public object ResponseDto { get; set; }
public object ErrorResponse { get; set; }
public IDictionary ExceptionData { get; set; }

I’d double-check to see where it might be possible that a non-serializable object is being logged. You can also configure your CsvRequestLogger to disable logging of different properties.

  • EnableSessionTracking disables Session logging
  • EnableResponseTracking disables ResponseDto
  • EnableErrorTracking disables ErrorResponse and ExceptionData logging

In the meantime I’m pushing through a change in CI that will allow you to register a handler to catch when an Exception occurs during WriteLogs where you’ll be able to put a breakpoint on and inspect the log entries to inspect these properties so you can find the culprit.

new CsvRequestLogger {
    OnWriteLogsError = (logEntries, ex) => ...
}

Ah. I believe the only memory stream I am using is when I return an image retrieved from our database.

Ok this change is available from the latest v5.8.1 that’s now available on MyGet.

I’ve also added OnSerialize callbacks you can hook into which will let you more easily find out all the types which are being serialized, e.g:

var serializedTypes = new HashSet<string>();
CsvSerializer.OnSerialize = obj => {
    if (obj != null) 
        serializedTypes.Add(obj.GetType().Name);
};

new CsvRequestLogger {
    OnWriteLogsError = (logEntries, ex) => {
        var log = LogManager.GetLogger(typeof(CsvRequestLogger));
        log.Error("Could not serialize logEntries, serializedTypes: " +
            string.Join(",", serializedTypes.ToArray()), ex);
    }
}

Once you know the problematic type you can RequestLogFilter to strip it out from the entry, e.g:

new CsvRequestLogger {
    RequestLogFilter = (req,entry) => {
        if (entry.ResponseDto is System.Drawing.Image)
            entry.ResponseDto = entry.ResponseDto.GetType().Name;
    }
}

FYI in the latest v5.8.1 on MyGet, I’ve also added an IgnoreTypes option where you can specify Types which to ignore serializing in logs, e.g:

new RequestLogsFeature {
    IgnoreTypes = { typeof(System.Drawing.Image) }
}