# # File:: %RS_TOOLSLIB%/pipeline/log/universal_log_fileoutputter.rb # Description:: Writes out a log to a file in an XML format # Optionally the log will be viewed. # # Author:: Derek Ward # Date:: 25th May 2011 # #---------------------------------------------------------------------------- # Example of XML format #---------------------------------------------------------------------------- # # # # # Map export starting... # Object has TXD name set to CHANGEME. # Object is close to vertex limit. # Object has invalid attribute set. # # # ... # # #---------------------------------------------------------------------------- # Uses #---------------------------------------------------------------------------- require 'pipeline/os/file' require 'pipeline/config/project' require 'pipeline/config/globals' #require 'pipeline/log/log' require 'date' require 'log4r/outputter/fileoutputter' require "systemu" require 'cgi' #---------------------------------------------------------------------------- # Implementation #---------------------------------------------------------------------------- module Pipeline # # == Description # XML file outputter formatter class. # class UniversalLogXMLFormatter < Log4r::Formatter attr_accessor :context, :num_events, :num_errors, :num_warnings # This method is invoked automatically by the Log4r system. # def format( event ) output ="" message = event.data.gsub( '"', '\'' ).gsub( /\n/, '' ) #output += "" #event.name? msg_type = msg_type_translate(event) # only call once per message. if ( event.tracer.nil? ) output += "<#{msg_type} context=\"#{event.name}\" timestamp=\"#{timestamp()}\">#{escape(message)}" else ti = parse_trace_info( event ) output += "<#{msg_type} context=\"#{event.name}\" timestamp=\"#{timestamp()}\">#{escape(message)}" # TODO DW - do something with ti[:filename] ti[:lineno] ti[:function]? end #output += "" output end # the current timestamp # def timestamp( ) DateTime.now.strftime('%Y-%m-%d %H:%M:%S') end # The message type ( translation to ulog message types ) # def msg_type_translate( event ) @num_events += 1 case Log4r::LNAMES[event.level] when 'Fatal' @num_errors += 1 "error" when 'Error' @num_errors += 1 "error" when 'Warn' @num_warnings += 1 "warning" when 'Debug' "debug" when 'Info' "message" else Log4r::LNAMES[event.level] end end # Replace reserved xml characters with appropriate entities. # def escape( message ) CGI.escapeHTML(message) end # Reset # def begin( context ) @num_events = 0 @num_errors = 0 @num_warnings = 0 @context = context end # Based on messages processed determine if it worthwhile presenting them # def should_show_log( ) (@num_errors > 0 or @num_warnings > 0) end #--------------------------------------------------------------------- # Private Methods #--------------------------------------------------------------------- private def parse_trace_info( event ) trace_info = {} matches = /^(.*)\:([0-9]+):in .([A-Za-z0-9_]+)./.match( event.tracer[0] ) if ( ( nil != matches ) and ( matches.size >= 4 ) ) then trace_info[:filename] = matches[1] trace_info[:lineno] = matches[2] trace_info[:function] = matches[3] else trace_info[:filename] = '' trace_info[:lineno] = '-1' trace_info[:function] = '' end # Return our hash with trace information trace_info end end # # == Description # Log4r file outputter - this writes out an xml file in a format that can be viewed # by our universal log viewer. # # class UniversalLogXMLFileOutputter < Log4r::FileOutputter ULOG_ENABLE_VIEWER = true # will completely disable the spwaning of the uilog viewer ULOG_VERSION = "1.0" # for backwards compatibility we mark up the version of the ulog format this outputter will output. XML_ENCODING = "utf-8" XML_VERSION = "1.0" ULOG_USER = ENV['USERNAME'] ULOG_COMP = ENV['COMPUTERNAME'] ULOG_DEFAULT_CONTEXT = "default" # the root context to insert into the ulog ( not the context of each message ) BUILDER_NAME = "builder" # user name that will not use the viewer by default - automation users never desire this ( something more robust required ultimately ) ULOG_CLIENT_BINARY = "UniversalLogClient.exe" # executable for viewing a ulog def initialize( _name, hash={}, view = false, context = ULOG_DEFAULT_CONTEXT ) super( _name, hash ) @context = context @view = view @is_open = true validate_hash( hash ) write_header() # automated builder would not want a ulog viewer to pop up unless they expressly request it. @enable_viewer = Config.instance.user.username.downcase.include?( BUILDER_NAME ) ? false : true # Ensure we close the log at program exit. This ensures we write # the XML footer tags to construct a valid XML document. at_exit do self.close() if @is_open self.view() end end def close return unless @is_open synch { begin # Finish XML tags, and close file. write_footer() super() pretty_print() @is_open = false rescue Exception => ex print_exception( ex ) end } end def view c = Pipeline::Config.instance viewer_command = "#{OS::Path::combine(c.toolsbin, ULOG_CLIENT_BINARY)} #{@filename}" status, stdout, stderr = systemu(viewer_command) if ( ULOG_ENABLE_VIEWER and @view and @formatter.should_show_log and @enable_viewer ) # TODO - DW : perhaps handle errors from the viewer? end #--------------------------------------------------------------------- # Protected #--------------------------------------------------------------------- protected def write_header( ) @out.write( "" ) @out.write( "" ) @out.write( "" ) end def write_footer( ) @out.write( "" ) @out.write( "" ) end def pretty_print( ) replace_filename = OS::Path.replace_ext( @filename, 'orig' ) FileUtils.cp( @filename, replace_filename ) File.open( replace_filename, "r" ) do |xmlinfile| File.open( @filename, 'w' ) do |xmloutfile| logxml = REXML::Document.new( xmlinfile ) logxml.xml_decl.encoding = XML_ENCODING fmt = REXML::Formatters::Pretty.new( ) fmt.write( logxml, xmloutfile ) end end FileUtils.rm( replace_filename ) end def create_formatter() @formatter = UniversalLogXMLFormatter.new @formatter.begin(@context) end def validate_hash( hash ) super( hash ) create_formatter() end end # Test this outputter. if __FILE__ == $0 config = Pipeline::Config.instance log_name = "#{OS::Path::get_basename( __FILE__ )}_test" log = Pipeline::Log::new( log_name ) log2 = Pipeline::Log::new( "log2" ) log.error( "This is an error with some xml characters in it that need escaping <> ; & \"" ) log2.error( "This is an error in another log" ) log.error( "This is another error" ) log2.warn( "This is a warning" ) log.info( "This is info" ) end end # Pipeline module # %RS_TOOLSLIB%/pipeline/log/universal_log_fileoutputter.rb