override protected void Append(Mammothcode.Public.Core.Core.LoggingEvent loggingEvent) { if (m_consoleOutputWriter != null) { IntPtr consoleHandle = IntPtr.Zero; if (m_writeToErrorStream) { // Write to the error stream consoleHandle = GetStdHandle(STD_ERROR_HANDLE); } else { // Write to the output stream consoleHandle = GetStdHandle(STD_OUTPUT_HANDLE); } // Default to white on black ushort colorInfo = (ushort)Colors.White; // see if there is a specified lookup LevelColors levelColors = m_levelMapping.Lookup(loggingEvent.Level) as LevelColors; if (levelColors != null) { colorInfo = levelColors.CombinedColor; } // Render the event to a string string strLoggingMessage = RenderLoggingEvent(loggingEvent); // get the current console color - to restore later CONSOLE_SCREEN_BUFFER_INFO bufferInfo; GetConsoleScreenBufferInfo(consoleHandle, out bufferInfo); // set the console colors SetConsoleTextAttribute(consoleHandle, colorInfo); // Using WriteConsoleW seems to be unreliable. // If a large buffer is written, say 15,000 chars // Followed by a larger buffer, say 20,000 chars // then WriteConsoleW will fail, last error 8 // 'Not enough storage is available to process this command.' // // Although the documentation states that the buffer must // be less that 64KB (i.e. 32,000 WCHARs) the longest string // that I can write out a the first call to WriteConsoleW // is only 30,704 chars. // // Unlike the WriteFile API the WriteConsoleW method does not // seem to be able to partially write out from the input buffer. // It does have a lpNumberOfCharsWritten parameter, but this is // either the length of the input buffer if any output was written, // or 0 when an error occurs. // // All results above were observed on Windows XP SP1 running // .NET runtime 1.1 SP1. // // Old call to WriteConsoleW: // // WriteConsoleW( // consoleHandle, // strLoggingMessage, // (UInt32)strLoggingMessage.Length, // out (UInt32)ignoreWrittenCount, // IntPtr.Zero); // // Instead of calling WriteConsoleW we use WriteFile which // handles large buffers correctly. Because WriteFile does not // handle the codepage conversion as WriteConsoleW does we // need to use a System.IO.StreamWriter with the appropriate // Encoding. The WriteFile calls are wrapped up in the // System.IO.__ConsoleStream internal class obtained through // the System.Console.OpenStandardOutput method. // // See the ActivateOptions method below for the code that // retrieves and wraps the stream. // The windows console uses ScrollConsoleScreenBuffer internally to // scroll the console buffer when the display buffer of the console // has been used up. ScrollConsoleScreenBuffer fills the area uncovered // by moving the current content with the background color // currently specified on the console. This means that it fills the // whole line in front of the cursor position with the current // background color. // This causes an issue when writing out text with a non default // background color. For example; We write a message with a Blue // background color and the scrollable area of the console is full. // When we write the newline at the end of the message the console // needs to scroll the buffer to make space available for the new line. // The ScrollConsoleScreenBuffer internals will fill the newly created // space with the current background color: Blue. // We then change the console color back to default (White text on a // Black background). We write some text to the console, the text is // written correctly in White with a Black background, however the // remainder of the line still has a Blue background. // // This causes a disjointed appearance to the output where the background // colors change. // // This can be remedied by restoring the console colors before causing // the buffer to scroll, i.e. before writing the last newline. This does // assume that the rendered message will end with a newline. // // Therefore we identify a trailing newline in the message and don't // write this to the output, then we restore the console color and write // a newline. Note that we must AutoFlush before we restore the console // color otherwise we will have no effect. // // There will still be a slight artefact for the last line of the message // will have the background extended to the end of the line, however this // is unlikely to cause any user issues. // // Note that none of the above is visible while the console buffer is scrollable // within the console window viewport, the effects only arise when the actual // buffer is full and needs to be scrolled. char[] messageCharArray = strLoggingMessage.ToCharArray(); int arrayLength = messageCharArray.Length; bool appendNewline = false; // Trim off last newline, if it exists if (arrayLength > 1 && messageCharArray[arrayLength-2] == '\r' && messageCharArray[arrayLength-1] == '\n') { arrayLength -= 2; appendNewline = true; } // Write to the output stream m_consoleOutputWriter.Write(messageCharArray, 0, arrayLength); // Restore the console back to its previous color scheme SetConsoleTextAttribute(consoleHandle, bufferInfo.wAttributes); if (appendNewline) { // Write the newline, after changing the color scheme m_consoleOutputWriter.Write(s_windowsNewline, 0, 2); } } }
/// <summary> /// This method is called by the <see cref="M:AppenderSkeleton.DoAppend(LoggingEvent)"/> method. /// </summary> /// <param name="loggingEvent">The event to log.</param> /// <remarks> /// <para> /// Writes the event to the console. /// </para> /// <para> /// The format of the output will depend on the appender's layout. /// </para> /// </remarks> override protected void Append(Mammothcode.Public.Core.Core.LoggingEvent loggingEvent) { string loggingMessage = RenderLoggingEvent(loggingEvent); // see if there is a specified lookup. LevelColors levelColors = m_levelMapping.Lookup(loggingEvent.Level) as LevelColors; if (levelColors != null) { // Prepend the Ansi Color code loggingMessage = levelColors.CombinedColor + loggingMessage; } // on most terminals there are weird effects if we don't clear the background color // before the new line. This checks to see if it ends with a newline, and if // so, inserts the clear codes before the newline, otherwise the clear codes // are inserted afterwards. if (loggingMessage.Length > 1) { if (loggingMessage.EndsWith("\r\n") || loggingMessage.EndsWith("\n\r")) { loggingMessage = loggingMessage.Insert(loggingMessage.Length - 2, PostEventCodes); } else if (loggingMessage.EndsWith("\n") || loggingMessage.EndsWith("\r")) { loggingMessage = loggingMessage.Insert(loggingMessage.Length - 1, PostEventCodes); } else { loggingMessage = loggingMessage + PostEventCodes; } } else { if (loggingMessage[0] == '\n' || loggingMessage[0] == '\r') { loggingMessage = PostEventCodes + loggingMessage; } else { loggingMessage = loggingMessage + PostEventCodes; } } if (m_writeToErrorStream) { // Write to the error stream Console.Error.Write(loggingMessage); } else { // Write to the output stream Console.Write(loggingMessage); } }