Skip to content

Commit

Permalink
Initial prototype code
Browse files Browse the repository at this point in the history
  • Loading branch information
c42f committed Feb 10, 2017
1 parent 928bff7 commit f6db371
Show file tree
Hide file tree
Showing 3 changed files with 386 additions and 6 deletions.
42 changes: 40 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,44 @@

[![Build Status](https://travis-ci.org/c42f/MicroLogging.jl.svg?branch=master)](https://travis-ci.org/c42f/MicroLogging.jl)

[![Coverage Status](https://coveralls.io/repos/c42f/MicroLogging.jl/badge.svg?branch=master&service=github)](https://coveralls.io/github/c42f/MicroLogging.jl?branch=master)

[![codecov.io](http://codecov.io/github/c42f/MicroLogging.jl/coverage.svg?branch=master)](http://codecov.io/github/c42f/MicroLogging.jl?branch=master)


## Design goals

A prototype for a new logging frontend for `Base`. This isn't meant to be a
fully featured logging framework, just a simple API which allows an efficient
implementation. Logging should be unified so that logs from all packages can be
directed to a common handler in a simple and consistent way.

### Minimalism of user-visible API

Logging should be so simple that you reach for `@info` rather than `println()`.

```julia
x = 42
@info "my value is x = $x"
```

but it should be extensible in the backend.


### Efficiency aims - messages you don't see are "free"

* Early-out to avoid formatting when the message will be filtered. With the
default of a per-module `Logger`, designed such that invisible messages cost
only a load, integer compare and branch.
* Ability to eliminate entire levels of verbose messages as dead code at compile
time (?)

### Custom log formatters and backends

* Module based log level control in a heirarchy for ease of use.
* Log pieces (module, message, file location, custom key/value pairs?) passed to
backend for custom formatting
* Non-Base logging frameworks should be able to define backends to send logs to:
stdout/stderr, files, the network, etc.
* The backend should be swappable, such that all packages using the frontend
logger get logs redirected there.


167 changes: 165 additions & 2 deletions src/MicroLogging.jl
Original file line number Diff line number Diff line change
@@ -1,5 +1,168 @@
module MicroLogging

# package code goes here
export Logger,
@debug, @info, @warn, @error,
@makelogger,
root_logger, configure_logging


#-------------------------------------------------------------------------------
immutable LogHandler
stream::IO
usecolor::Bool
end

LogHandler(stream::IO) = LogHandler(stream, true)

function logmsg(handler::LogHandler, loggername, level, location, msg)
if level == Debug ; color = :cyan ; levelstr = "DEBUG:"
elseif level == Info ; color = :blue ; levelstr = "INFO :"
elseif level == Warn ; color = :yellow ; levelstr = "WARN :"
elseif level == Error ; color = :red ; levelstr = "ERROR:"
else color = :dark_white ; levelstr = string(level)
end
if handler.usecolor
Base.print_with_color(color, handler.stream, levelstr)
else
print(handler.stream, levelstr)
end
fullmsg = " [($(loggername)) $(location[1]):$(location[2])]: $msg\n"
Base.print(handler.stream, fullmsg)
end



#-------------------------------------------------------------------------------
abstract AbstractLogLevel

immutable LogLevel <: AbstractLogLevel
level::Int
end
const Debug = LogLevel(-10)
const Info = LogLevel(0)
const Warn = LogLevel(10)
const Error = LogLevel(20)

Base.:<=(l1::LogLevel, l2::LogLevel) = l1.level <= l2.level



#-------------------------------------------------------------------------------
type Logger{L}
name::Symbol
min_level::L
handler
children::Vector{Logger}
end

Logger{L}(name, parent::Logger{L}) = Logger{L}(Symbol(name), parent.min_level,
parent.handler, Vector{Module}())
Logger(name, handler, level=Info) = Logger{typeof(level)}(Symbol(name), level,
handler, Vector{Module}())
const _root_logger = Logger(:Main, LogHandler(STDERR))

Base.push!(parent::Logger, child) = push!(parent.children, child)

const logger = _root_logger
root_logger() = _root_logger

function configure_logging(; kwargs...)
configure_logging(root_logger(); kwargs...)
end

function configure_logging(logger; level=nothing, handler=nothing)
if level !== nothing
logger.min_level = level
end
if handler !== nothing
logger.handler = handler
end
for child in logger.children
configure_logging(child; level=level, handler=handler)
end
end

function configure_logging(m::Module; kwargs...)
mod = find_logger_module(m)
configure_logging(mod.logger; kwargs...)
end


shouldlog(logger::Logger, level) = logger.min_level <= level

logmsg(logger::Logger, level, location, msg) = logmsg(logger.handler, logger.name, level, location, msg)


function find_logger_module(m::Module)
while module_name(m) !== :Main
if isdefined(m, :logger)
return m
end
m = module_parent(m)
end
return MicroLogging
end


# Logging macros
for (mname, level) in [(:debug, Debug),
(:info, Info),
(:warn, Warn),
(:error, Error)]
@eval macro $mname(exs...)
if length(exs) == 1
mod = find_logger_module(current_module())
logger_ex = :($mod.logger)
msg = esc(exs[1])
elseif length(exs) == 2
logger_ex = esc(exs[1])
msg = esc(exs[2])
else
error("@$mname must be called with one or two arguments")
end
quote
logger = $logger_ex
if shouldlog(logger, $($level))
logmsg(logger, $($level), (@__FILE__, @__LINE__), $msg)

This comment has been minimized.

Copy link
@quinnj

quinnj Feb 11, 2017

hey @c42f, did including the @__LINE__ here work for you? I tried in the HTTP.jl package to do something like this, but quoting the @__LINE__ returned the line of the original macro definition instead of where I actually used my @log macro.

This comment has been minimized.

Copy link
@c42f

c42f Feb 11, 2017

Author Owner

Hah, good question! I'm afraid you're quite right - this doesn't work at all :-(

A proper location should be available by calling backtrace() inside logmsg, but the theory was that having a simple file and line number gives enough information in many cases, and should be quite a lot cheaper as a default mechanism.

end
nothing
end
end
end


#-------------------------------------------------------------------------------
# Create per-module logger


"""
Create a logger for the current module
"""
macro makelogger()
modname = string(current_module())
parent_logger = find_logger_module(module_parent(current_module())).logger
esc(
quote
const logger = Logger($modname, $parent_logger)
push!($parent_logger, logger)
end
)
end


#=
macro logtrace(func)
assert(isa(func, Expr) && func.head == :function && func.args[2].head == :block)
enter_msg = "Enter $(func.args[1].args[1])"
exit_msg = "Exit $(func.args[1].args[1])"
unshift!(func.args[2].args, :(MicroLogging.@trace $enter_msg))
push!(func.args[2].args, :(MicroLogging.@trace $exit_msg))
esc(func)
end
=#

end




end # module
Loading

0 comments on commit f6db371

Please sign in to comment.