Files
gtav-src/tools_ng/lib/pipeline/log/universal_log_fileoutputter.rb
2025-09-29 00:52:08 +02:00

293 lines
8.3 KiB
Ruby
Executable File

#
# 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 <derek@rockstarnorth.com>
# Date:: 25th May 2011
#
#----------------------------------------------------------------------------
# Example of XML format
#----------------------------------------------------------------------------
#<?xml version="1.0" encoding="utf-8"?>
# <ulog name="log_name" version="1.0" user="david.muir" machine="EDIW-DMUIR3">
# <context name="default" />
# <context name="map_export">
# <message timestamp="">Map export starting...</message>
# <error context="DT1_01_Building03" timestamp="">Object has TXD name set to CHANGEME.</error>
# <warning context="DT1_01_Building06" timestamp="">Object is close to vertex limit.</warning>
# <debug context="DT1_01_Building07" timestamp="">Object has invalid attribute set.</debug>
# </context>
# <context name="map_export_validation">
# ...
# </context>
# </ulog>
#----------------------------------------------------------------------------
# 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 += "<context name=\"#{@context}\">" #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)}</#{msg_type}>"
else
ti = parse_trace_info( event )
output += "<#{msg_type} context=\"#{event.name}\" timestamp=\"#{timestamp()}\">#{escape(message)}</#{msg_type}>"
# TODO DW - do something with ti[:filename] ti[:lineno] ti[:function]?
end
#output += "</context>"
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( "<?xml version=\"#{XML_VERSION}\" encoding=\"#{XML_ENCODING}\" ?>" )
@out.write( "<ulog name=\"#{@name}\" version=\"#{ULOG_VERSION}\" user=\"#{ULOG_USER}\" machine=\"#{ULOG_COMP}\" >" )
@out.write( "<context name=\"#{@context}\">" )
end
def write_footer( )
@out.write( "</context>" )
@out.write( "</ulog>" )
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