; docformat = 'rst'
;+
; Logger object to control logging.
;
; :Properties:
; name : type=string
; name of the logger
; parent : private
; parent logger
; level : type=long
; current level of logging: 0 (none), 1 (critial), 2 (error),
; 3 (warning), 4 (info), or 5 (debug); can be set to an array of levels
; which will be cascaded up to the parents of the logger with the
; logger taking the last level and passing the previous ones up to its
; parent; only messages with levels lower or equal to than the logger
; level will be logged
; time_format : type=string
; Fortran style format code to specify the format of the time in the
; `FORMAT` property; the default value formats the time/date like
; "2003-07-08 16:49:45.891"
; format : type=string
; format string for messages, default value for format is::
;
; '%(time)s %(levelname)s: %(routine)s: %(message)s'
;
; where the possible names to include are: "time", "levelname",
; "routine", "stacktrace", "name", and "message".
;
; Note that the time argument will first be formatted using the
; `TIME_FORMAT` specification
; filename : type=string
; filename to send append output to; set to empty string to send output
; to `stderr`
; clobber : type=boolean
; set, along with filename, to clobber pre-existing file
; output : type=strarr
; output sent to the logger already
; _extra : type=keywords
; any keyword accepted by `MGffLogger::setProperty`
;-
;+
; Get the minimum level value of this logger and all its parents.
;
; :Private:
;
; :Returns:
; long
;-
function mgfflogger::_getLevel
compile_opt strictarr
return, obj_valid(self.parent) $
? (self.parent->_getLevel() < self.level) $
: self.level
end
;+
; Finds the name of an object, even if it does not have a `NAME` property.
; Returns the empty string if the object does not have a `NAME` property.
;
; :Private:
;
; :Returns:
; string
;
; :Params:
; obj : in, required, type=object
; object to find name of
;-
function mgfflogger::_askName, obj
compile_opt strictarr
catch, error
if (error ne 0L) then begin
catch, /cancel
return, ''
endif
obj->getProperty, name=name
return, name
end
;+
; Returns an immediate child of a container by name.
;
; :Private:
;
; :Returns:
; object
;
; :Params:
; name : in, required, type=string
; name of immediate child
; container : in, required, type=object
; container to search children of
;-
function mgfflogger::_getChildByName, name, container
compile_opt strictarr
for i = 0L, container.children->count() - 1L do begin
child = container.children->get(position=i)
childName = self->_askName(child)
if (childName eq name) then return, child
endfor
return, obj_new()
end
;+
; Traverses a hierarchy of named objects using a path of names delimited with
; /'s.
;
; :Returns:
; object
;
; :Params:
; name : in, required, type=string
; path of names to the desired object; names are delimited with /'s
;-
function mgfflogger::getByName, name
compile_opt strictarr
tokens = strsplit(name, '/', /extract, count=ntokens)
child = self
for depth = 0L, ntokens - 1L do begin
newChild = self->_getChildByName(tokens[depth], child)
if (~obj_valid(newChild)) then begin
newChild = obj_new('MGffLogger', name=tokens[depth], parent=child)
child.children->add, newChild
endif
child = newChild
endfor
return, child
end
;+
; Set properties.
;-
pro mgfflogger::getProperty, level=level, $
format=format, time_format=time_format, $
name=name, $
filename=filename, $
output=output
compile_opt strictarr
if (arg_present(level)) then level = self.level
if (arg_present(format)) then format = self.format
if (arg_present(time_format)) then time_format = self.time_format
if (arg_present(name)) then name = self.name
if (arg_present(filename)) then filename = self.filename
if (arg_present(output)) then begin
if (self.filename ne '') then begin
output = strarr(file_lines(self.filename))
openr, lun, self.filename, /get_lun
readf, lun, output
endif
endif
end
;+
; Get properties.
;-
pro mgfflogger::setProperty, level=level, $
format=format, time_format=time_format, $
filename=filename, clobber=clobber
compile_opt strictarr
case n_elements(level) of
0:
1: self.level = level
else: begin
self.level = level[n_elements(level) - 1L]
if (obj_valid(self.parent)) then begin
self.parent->setProperty, level=level[0:n_elements(level) - 2L]
endif
end
endcase
if (n_elements(format) gt 0L) then self.format = format
if (n_elements(time_format) gt 0L) then self.time_format = time_format
if (n_elements(filename) gt 0L) then self.filename = filename
if (keyword_set(clobber) && n_elements(filename) gt 0L) then begin
if (file_test(filename)) then file_delete, filename
endif
end
;+
; Insert the stack trace for the last error message into the log. Since stack
; traces are from run-time crashes they are considered to be at the CRITICAL
; level.
;
; :Keywords:
; back_levels : in, optional, private, type=boolean
; number of levels to go back in the stack trace beyond the normal ones;
; should be set to 1 if calling this routine from `MG_LOG` for example
;-
pro mgfflogger::insertLastError, back_levels=back_levels
compile_opt strictarr
_back_levels = n_elements(back_levels) eq 0L ? 0 : back_levels
help, /last_message, output=helpOutput
if (n_elements(helpOutput) eq 1L && helpOutput[0] eq '') then return
self->print, 'Stack trace for error', level=1, back_levels=_back_levels + 1L
if (self.filename eq '') then begin
lun = -2L
endif else begin
if (file_test(self.filename)) then begin
openu, lun, self.filename, /get_lun, /append
endif else begin
openw, lun, self.filename, /get_lun
endelse
endelse
printf, lun, transpose(helpOutput)
if (lun ge 0L) then free_lun, lun
end
;+
; Log message to given level.
;
; :Params:
; msg : in, required, type=string
; message to print
;
; :Keywords:
; level : in, optional, type=long
; level of message
; back_levels : in, optional, private, type=boolean
; number of levels to go back in the stack trace beyond the normal ones;
; should be set to 1 if calling this routine from `MG_LOG` for example
;-
pro mgfflogger::print, msg, level=level, back_levels=back_levels
compile_opt strictarr
_back_levels = n_elements(back_levels) eq 0L ? 0 : back_levels
if (self.filename eq '') then begin
lun = -2L
endif else begin
if (file_test(self.filename)) then begin
openu, lun, self.filename, /get_lun, /append
endif else begin
openw, lun, self.filename, /get_lun
endelse
endelse
if (level le self->_getLevel()) then begin
stack = scope_traceback(/structure, /system)
vars = { time: string(systime(/julian), format='(' + self.time_format + ')'), $
levelname: strupcase(self.levelNames[level - 1L]), $
routine: stack[n_elements(stack) - 2L - _back_levels].routine, $
stacktrace: strjoin(stack[0:n_elements(stack) - 2L - _back_levels].routine, '->'), $
name: self.name, $
message: msg $
}
s = mg_subs(self.format, vars)
printf, lun, s
endif
if (lun ge 0L) then free_lun, lun
end
;+
; Free resources.
;-
pro mgfflogger::cleanup
compile_opt strictarr
if (obj_valid(self.parent)) then begin
(self.parent).children->remove, self
endif
obj_destroy, self.children
end
;+
; Create logger object.
;
; :Returns:
; 1 for success, 0 for failure
;-
function mgfflogger::init, parent=parent, name=name, _extra=e
compile_opt strictarr
self.parent = n_elements(parent) eq 0L ? obj_new() : parent
self.name = n_elements(name) eq 0L ? '' : name
self.children = obj_new('IDL_Container')
self.time_format = 'C(CYI4.4, "-", CMOI2.2, "-", CDI2.2, " ", CHI2.2, ":", CMI2.2, ":", CSF06.3)'
self.format = '%(time)s %(levelname)s: %(routine)s: %(message)s'
self.level = 0L
self.levelNames = ['Critical', 'Error', 'Warning', 'Informational', 'Debug']
self->setProperty, _extra=e
return, 1
end
;+
; Define instance variables.
;
; :Fields:
; parent
; parent `MGffLoffer` object
; name
; name of the loffer
; children
; `IDL_Container` of children loggers
; level
; current level of logging: 0=none, 1=critical, 2=error, 3=warning,
; 4=informational, or 5=debug; only messages with a level lower or equal
; to this this value will be logged
; levelNames
; names for the different levels
; filename
; filename to send output to
; time_format
; Fortran format codes for calendar output
; format
; format code to send output to
;-
pro mgfflogger__define
compile_opt strictarr
define = { MGffLogger, $
parent: obj_new(), $
name: '', $
children: obj_new(), $
level: 0L, $
levelNames: strarr(5), $
filename: '', $
time_format: '', $
format: '' $
}
end