-
-
Notifications
You must be signed in to change notification settings - Fork 96
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
console work #258
console work #258
Changes from 5 commits
8624090
6092414
6ea7792
2cd03c7
a99e865
0f42d6b
3cc6fbe
e5d02af
0bc7f0e
a9fe44b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -12,10 +12,12 @@ | |
/// | ||
/// Changes: | ||
/// Changed namespace to Expecto.Logging and file name to Logging.fs - Anthony Lloyd - 11 Jun 2018 | ||
/// Add IFlushable - Anthony Lloyd - 19 Jun 2018 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why keep a changelog at the top? Feels a bit year 2000. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I read that it was a good thing to do when making changes to Apache code. To help when back porting the code. Could be got from git but it contains a bit more specific info. |
||
/// | ||
namespace Expecto.Logging | ||
|
||
open System | ||
open System.Text | ||
|
||
/// The log level denotes how 'important' the gauge or event message is. | ||
[<CustomEquality; CustomComparison>] | ||
|
@@ -293,6 +295,9 @@ module LoggerEx = | |
|
||
// TODO: timeXXX functions | ||
|
||
type IFlushable = | ||
abstract Flush : unit -> unit | ||
|
||
type LoggingConfig = | ||
{ /// The `timestamp` function should preferably be monotonic and not 'jumpy' | ||
/// or take much time to call. | ||
|
@@ -936,6 +941,80 @@ type CombiningTarget(name, otherLoggers: Logger list) = | |
|> Async.Parallel | ||
|> Async.Ignore // Async<unit> | ||
|
||
type ANSIConsoleLogger(name, minLevel, ?consoleSemaphore) = | ||
let sem = defaultArg consoleSemaphore (obj()) | ||
let options = Literate.LiterateOptions.create() | ||
let tokenise = LiterateTokenisation.tokeniseMessage | ||
let buffer = StringBuilder() | ||
|
||
let colorForWhite = | ||
if Console.BackgroundColor = ConsoleColor.White then "\x1B[90m" else "\x1B[97m" | ||
let colorReset = "\x1B[0m" | ||
|
||
let colorANSI = function | ||
| ConsoleColor.White -> colorForWhite | ||
| ConsoleColor.Gray -> "\x1B[31m" | ||
| ConsoleColor.DarkGray -> "\x1B[90m" | ||
| ConsoleColor.Yellow -> "\x1B[93m" | ||
| ConsoleColor.Red -> "\x1B[91m" | ||
| ConsoleColor.Blue -> "\x1B[33m" | ||
| ConsoleColor.Magenta -> "\x1B[95m" | ||
| ConsoleColor.Cyan -> "\x1B[96m" | ||
| ConsoleColor.Green -> "\x1B[92m" | ||
| _ -> "\x1B[334m" | ||
|
||
let atomicallyWriteColouredTextToConsole sem (parts: (string * ConsoleColor) list) = | ||
lock sem <| fun _ -> | ||
let mutable currentColour = Console.ForegroundColor | ||
parts |> List.iter (fun (text, colour) -> | ||
if currentColour <> colour then | ||
colorANSI colour |> buffer.Append |> ignore | ||
currentColour <- colour | ||
buffer.Append text |> ignore | ||
) | ||
buffer.Append colorReset |> ignore | ||
|
||
let atomicallyWriteTextToConsole sem (parts: (string * ConsoleColor) list) = | ||
lock sem <| fun _ -> | ||
parts |> List.iter (fun (text, _) -> | ||
buffer.Append text |> ignore | ||
) | ||
|
||
let textToBuffer = | ||
if Console.IsOutputRedirected then atomicallyWriteTextToConsole | ||
else atomicallyWriteColouredTextToConsole | ||
|
||
let writeColourisedThenNewLine message = | ||
[ yield! tokenise options message | ||
yield Environment.NewLine, Literate.Text ] | ||
|> List.map (fun (s, t) -> s, options.theme(t)) | ||
|> textToBuffer sem | ||
|
||
interface IFlushable with | ||
member __.Flush() = | ||
lock sem (fun _ -> | ||
buffer.ToString() |> Console.Write | ||
buffer.Clear() |> ignore | ||
) | ||
|
||
interface Logger with | ||
member __.name = name | ||
member x.logWithAck level msgFactory = | ||
if level >= minLevel then | ||
writeColourisedThenNewLine (msgFactory level) | ||
Expecto.ProgressIndicator.pause (fun () -> | ||
(x :> IFlushable).Flush() | ||
) | ||
async.Return() | ||
member x.log level msgFactory = | ||
if level >= minLevel then | ||
writeColourisedThenNewLine (msgFactory level) | ||
if level=Error then | ||
Expecto.ProgressIndicator.pause (fun () -> | ||
(x :> IFlushable).Flush() | ||
) | ||
async.Return() | ||
|
||
module Global = | ||
/// This is the global semaphore for colourising the console output. Ensure | ||
/// that the same semaphore is used across libraries by using the Logary | ||
|
@@ -945,7 +1024,7 @@ module Global = | |
/// The global default configuration, which logs to Console at Info level. | ||
let defaultConfig = | ||
{ timestamp = fun () -> DateTimeOffset.timestamp DateTimeOffset.UtcNow | ||
getLogger = fun name -> LiterateConsoleTarget(name, Info) :> Logger | ||
getLogger = fun name -> ANSIConsoleLogger(name, Info) :> Logger | ||
consoleSemaphore = consoleSemaphore } | ||
|
||
let private config = | ||
|
@@ -980,6 +1059,10 @@ module Global = | |
member x.logWithAck level msgFactory = | ||
withLogger (fun logger -> logger.logWithAck level (msgFactory >> ensureName)) | ||
|
||
interface IFlushable with | ||
member __.Flush() = | ||
if logger :? IFlushable then (logger :?> IFlushable).Flush() | ||
|
||
let internal getStaticLogger (name: string []) = | ||
Flyweight name | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,70 @@ | ||
namespace Expecto | ||
|
||
open System | ||
open System.Threading | ||
|
||
module internal ProgressIndicator = | ||
|
||
let private hideCursor = "\x1B[?25l" | ||
let private showCursor = "\x1B[?25h" | ||
let private animation = @"|/-\" | ||
|
||
let mutable private textValue = String.Empty | ||
let mutable private eraseLine = String.Empty | ||
let private percentValue = ref 0 | ||
let private isRunning = ref false | ||
|
||
let text s = | ||
textValue <- s | ||
let lineLength = textValue.Length + 7 | ||
eraseLine <- String('\b', lineLength) | ||
|
||
let update percent = | ||
percentValue := max percent 0 |> min 100 | ||
|
||
let start() = | ||
lock isRunning (fun () -> | ||
if !isRunning then false | ||
else | ||
isRunning := true | ||
if not Console.IsOutputRedirected then | ||
hideCursor |> stdout.Write | ||
Thread(fun () -> | ||
let start = DateTime.UtcNow | ||
while !isRunning do | ||
lock isRunning (fun () -> | ||
if !isRunning then | ||
let p = !percentValue | ||
let t = (DateTime.UtcNow - start).TotalSeconds | ||
let a = animation.[int t % animation.Length] | ||
let percent = | ||
if p=100 then [|'1';'0';'0';'%';' ';a|] | ||
elif p<10 then [|' ';' ';char(48+p);'%';' ';a|] | ||
else [|' ';char(48+p/10);char(48+p%10);'%';' ';a|] | ||
eraseLine + textValue + String percent + eraseLine |> stdout.Write | ||
Console.Out.Flush() | ||
) | ||
Thread.Sleep 250 | ||
).Start() | ||
true | ||
) | ||
|
||
let stop() = | ||
lock isRunning (fun() -> | ||
if !isRunning then | ||
isRunning := false | ||
if not Console.IsOutputRedirected then | ||
eraseLine + String(' ', eraseLine.Length) + eraseLine + showCursor |> stdout.Write | ||
Console.Out.Flush() | ||
) | ||
|
||
let pause f = | ||
lock isRunning (fun () -> | ||
if !isRunning then | ||
stop(); f(); start() |> ignore | ||
else f() | ||
) | ||
|
||
// TODO | ||
// 123/199 tests | ||
// test output, where can I be |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Remember that Stopwatch's ticks differs between operating systems and is not the same as DateTime/BCL ticks.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, you need to use Stopwatch.Frequency to convert between seconds and Stopwatch ticks. It does this in the lines just above here.