Last active
July 28, 2017 21:30
-
-
Save gtvfx/0c74ad6090348d58bab096a8b95546c8 to your computer and use it in GitHub Desktop.
Maxscript Logger object for formatting differing levels of information to the console. Inspired by the Python Logger module.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/*************************************************************************************************** | |
Copyright (C) 2013 - 2017 Gavyn Thompson | |
This program is free software: you can redistribute it and/or modify | |
it under the terms of the GNU General Public License as published by | |
the Free Software Foundation; either version 3 of the License, or | |
(at your option) any later version. | |
This program is distributed in the hope that it will be useful, | |
but WITHOUT ANY WARRANTY; without even the implied warranty of | |
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
GNU General Public License for more details. | |
You should have received a copy of the GNU General Public License | |
along with this program. if not, see <http://www.gnu.org/licenses/>. | |
***************************************************************************************************/ | |
/*************************************************************************************************** | |
Author: Gavyn Thompson | |
Company: GTVFX | |
Website: https://github.com/gtvfx | |
Email: gftvfx@gmail.com | |
ScriptVersion: | |
Updated: | |
[Purpose] | |
***************************************************************************************************/ | |
/* | |
__HELP__ | |
Constructor: Logger | |
Instantiated Global: Logger | |
Methods: | |
<Void> SetLevel lvl | |
-- Set the logger level ( #none, #info, #debug ) | |
<Void> SetVerbosity v | |
-- Set the verbosity of the debugger | |
<Bool> IsEnabledFor lvl | |
-- Returns true if the supplied log level is enabled, otherwise false | |
<Array> GetEffectiveLevel | |
-- Returns an array where index 1 is the current log level enumeratoin and index 2 is the current log level integer | |
<Int> GetVerbosity | |
-- returns the current verbosity level | |
Debug msg args:#() kwargsDict: verbosity:1 cls: | |
-- if #debug level is enabled then prints a DEBUG message | |
-- the args array is used with the Format method of this class. Expects the msg to comply with the required syntax. | |
-- The kwargsDict is an optional supplied hashTable to the logger will print out the Keys and Values for along witht eh msg | |
-- verbosity is an integer value. Default is 1, higher numbers require a higher verbosity level in order to execute. | |
-- cls is the instance of the class the Logger is being run from. Either leave unsupplied or supply the 'this' expression | |
Info msg args:#() kwargsDict: cls: | |
-- if #info or #debug level is enabled then prints an INFO message | |
-- the args array is used with the Format method of this class. Expects the msg to comply with the required syntax. | |
-- The kwargsDict is an optional supplied hashTable to the logger will print out the Keys and Values for along witht eh msg | |
-- cls is the instance of the class the Logger is being run from. Either leave unsupplied or supply the 'this' expression | |
Warning msg args:#() kwargsDict: cls: message:True | |
-- prints a WARNING message. This is not affected by log level | |
-- The optional message flag will dispaly a MessageBox if set to True | |
-- the args array is used with the Format method of this class. Expects the msg to comply with the required syntax. | |
-- The kwargsDict is an optional supplied hashTable to the logger will print out the Keys and Values for along witht eh msg | |
-- cls is the instance of the class the Logger is being run from. Either leave unsupplied or supply the 'this' expression | |
Error msg args:#() kwargsDict: cls: | |
-- prints an ERROR message. This is not affected by log level | |
-- the args array is used with the Format method of this class. Expects the msg to comply with the required syntax. | |
-- The kwargsDict is an optional supplied hashTable to the logger will print out the Keys and Values for along witht eh msg | |
-- cls is the instance of the class the Logger is being run from. Either leave unsupplied or supply the 'this' expression | |
Critical msg args:#() kwargsDict: cls: | |
-- prints an CRITICAL message. This is not affected by log level | |
-- the args array is used with the Format method of this class. Expects the msg to comply with the required syntax. | |
-- The kwargsDict is an optional supplied hashTable to the logger will print out the Keys and Values for along witht eh msg | |
-- Performs a THROW operation which ends all running threads with a Runtime Error | |
-- cls is the instance of the class the Logger is being run from. Either leave unsupplied or supply the 'this' expression | |
Log msg statement:"LOG" lvl:( ::Enum_LoggerLevel.info ) args:#() kwargsDict: cls: | |
-- A custom log | |
-- you can set the custom statement. This will appear inplace of 'INFO' or 'DEBUG' | |
-- you can set the lvl you wish. Either #info or #debug | |
-- the args array is used with the Format method of this class. Expects the msg to comply with the required syntax. | |
-- The kwargsDict is an optional supplied hashTable to the logger will print out the Keys and Values for along witht eh msg | |
-- cls is the instance of the class the Logger is being run from. Either leave unsupplied or supply the 'this' expression | |
Format msg args:#() | |
-- Allows you to format a string in place | |
-- Uses more similar to Python sintax where arg variables in the string must be in format: {<index>} | |
-- where <index> matches the arguments index in the args array | |
-- Example: tstStr = Logger.format "This {1} a {2} to {3} if {4} works" args:#("is", "test", "see", "this") | |
__END__ | |
*/ | |
::PYTHON_RETURN | |
struct Enum_LoggerLevel | |
( | |
none = #none, | |
info = #info, | |
debug = #debug | |
) | |
struct Enum_LevelVal | |
( | |
none = 0, | |
info = 1, | |
debug = 2 | |
) | |
Enum_LoggerLevel = Enum_LoggerLevel() | |
Enum_LevelVal = Enum_LevelVal() | |
struct Logger | |
( | |
public | |
fn DumpListenerTextToFile filepath = | |
( | |
-- 3dsmax has a really slow write speed to the network ( Seems most prevelant on ISOLON storage ) | |
-- writing local and copying up is a fast work-around | |
local temp_file = (( GetDir #temp ) + "\\mxs_log.log" ) | |
if ( DoesFileExist temp_file ) then | |
( | |
DeleteFile temp_file | |
) | |
if ( DoesFileExist filepath ) then | |
( | |
DeleteFile filepath | |
) | |
-- This has to be a gloabal variable, because reasons | |
global ListenerText -- Declare variable to capture the Listener to | |
SetListenerSel #(0,-1) -- Select all the text | |
::ListenerText = GetListenerSelText() -- Get selected text | |
local strm = openFile temp_file mode:"w" | |
format ( ListenerText as string ) to:strm | |
close strm | |
-- Copy the local file to the desired filepath | |
CopyFile temp_file filepath | |
-- Clear the global from memory | |
::ListenerText = undefined | |
format "----- Listener log dumped to: % -----\n" filepath | |
), | |
fn EndLogging = | |
( | |
flushLog() -- ensures that we push everything from the listener inot the log file before closing | |
closeLog() | |
), | |
fn EnableLogging filepath = | |
( | |
this.EndLogging() | |
openLog filepath mode:"w" outputOnly:False | |
), | |
fn GetLogDir = | |
( | |
local logDir = ( GetDir #temp ) + @"\Logger\" | |
MakeDir logDir | |
logDir | |
), | |
fn GetLogFilepath = | |
( | |
local base_filename = "Logger" | |
local file_xt = ".log" | |
local filename = base_filename + "_" + (( abs ( GetHashValue ( ::mxs.DateTime() ) 0 )) as string ) + file_xt | |
( ( this.GetLogDir() ) + filename ) | |
), | |
fn CollectOldFiles dir threshold_days:12 = | |
( | |
local pyCmd = StringStream "" | |
format " | |
import os, time | |
def collect_old_files(file_dir, threshold_days=None): | |
if threshold_days == None: | |
threshold_days = 0 | |
threshold_time = ( time.time() ) - ( 60 * 60* 24 * threshold_days ) | |
file_list = [os.path.join(file_dir, f) for f in os.listdir(file_dir) if '.log' in f and ( os.path.getmtime(os.path.join(file_dir, f)) <= threshold_time )] | |
return file_list | |
files = collect_old_files(r'%', %) | |
arr = '#({0})'.format(','.join([str('@\"'+str(n)+'\"') for n in files])) | |
MaxPlus.Core.EvalMAXScript('PYTHON_RETURN = {0}'.format(arr)) | |
" ( TrimRight dir "\\" ) threshold_days to:pyCmd | |
python.execute ( pyCmd as string ) | |
::PYTHON_RETURN | |
), | |
fn CleanUpLogs threshold_days:12 = | |
( | |
local old_files = this.CollectOldFiles ( this.GetLogDir() ) threshold_days:threshold_days | |
if old_files.count != 0 then | |
( | |
this.info "Cleaning up old logs" args:#() cls:this | |
for f in old_files do | |
( | |
try | |
( | |
DeleteFile f | |
) | |
catch | |
( | |
this.Error "Unable to clean up {1}" args:#(f) cls:this | |
) | |
) | |
) | |
), | |
fn OpenLogDir = | |
( | |
ShellLaunch ( this.GetLogDir() ) "" | |
), | |
fn SetLevel lvl = | |
( | |
local vals = this.GetLevelValues lvl | |
if ( vals != undefined ) then | |
( | |
this.level = vals[1] | |
level_val = vals[2] | |
) | |
vals | |
), | |
fn SetVerbosity v = | |
( | |
if ( v > this._maxVerbosity ) then | |
( | |
this.error "Maximum verbosity is {1}" args:#( (this._maxVerbosity as string) ) cls:this | |
) | |
else | |
( | |
this.verbosity = v | |
) | |
), | |
fn IsEnabledFor lvl = | |
( | |
local lvlVals = this.GetLevelValues lvl | |
if ( lvlVals == undefined ) then return undefined | |
lvl = lvlVals[1] | |
case lvl of | |
( | |
( ::Enum_LoggerLevel.none ): | |
( | |
this.level == lvl | |
) | |
( ::Enum_LoggerLevel.info ): | |
( | |
if ( this.level == lvl ) or ( this.level == ( ::Enum_LoggerLevel.debug ) ) then | |
( | |
true | |
) | |
else | |
( | |
false | |
) | |
) | |
( ::Enum_LoggerLevel.debug ): | |
( | |
this.level == lvl | |
) | |
) | |
), | |
fn GetEffectiveLevel = | |
( | |
#(this.level, this.level_val) | |
), | |
fn GetVerbosity = | |
( | |
this.verbosity | |
), | |
fn Debug msg args:#() kwargsDict: verbosity:1 cls: = | |
( | |
if ( verbosity <= this.verbosity ) then | |
( | |
if ( this.GetEffectiveLevel() )[1] == ( ::Enum_LoggerLevel.debug ) then | |
( | |
if ( args.count != 0 ) then | |
( | |
msg = this.format msg args:args | |
) | |
local extraStr = "" | |
if ( kwargsDict != unsupplied ) and ( ClassOf kwargsDict == dotNetObject ) then | |
( | |
extraStr = " " + this.ConcatStrFromHashtable kwargsDict | |
) | |
if ( cls != unsupplied ) then cls = ( this.GetClassTitle cls ) else cls = "" | |
format "DEBUG :: [%] *----- % -----*\n" cls ( msg + extraStr ) | |
) | |
) | |
), | |
fn Info msg args:#() kwargsDict: cls: = | |
( | |
if ( this.GetEffectiveLevel() )[2] != ( ::Enum_LoggerLevel.none ) then | |
( | |
if ( args.count != 0 ) then | |
( | |
msg = this.format msg args:args | |
) | |
local extraStr = "" | |
if ( kwargsDict != unsupplied ) and ( ClassOf kwargsDict == dotNetObject ) then | |
( | |
extraStr = " " + this.ConcatStrFromHashtable kwargsDict | |
) | |
if ( cls != unsupplied ) then cls = ( this.GetClassTitle cls ) else cls = "" | |
format "INFO :: [%] ***** % *****\n" cls ( msg + extraStr ) | |
) | |
), | |
fn Warning msg args:#() kwargsDict: cls: message:True = | |
( | |
if ( args.count != 0 ) then | |
( | |
msg = this.format msg args:args | |
) | |
local extraStr = "" | |
if ( kwargsDict != unsupplied ) and ( ClassOf kwargsDict == dotNetObject ) then | |
( | |
extraStr = " " + this.ConcatStrFromHashtable kwargsDict | |
) | |
if ( cls != unsupplied ) then cls = ( this.GetClassTitle cls ) else cls = "" | |
if message then | |
( | |
messageBox ( msg + extraStr ) title:( cls + ":" ) | |
) | |
format "WARNING :: [%] !!---------- % ----------!!\n" cls ( msg + extraStr ) | |
), | |
fn Error msg args:#() kwargsDict: cls: = | |
( | |
if ( args.count != 0 ) then | |
( | |
msg = this.format msg args:args | |
) | |
local extraStr = "" | |
if ( kwargsDict != unsupplied ) and ( ClassOf kwargsDict == dotNetObject ) then | |
( | |
extraStr = " " + this.ConcatStrFromHashtable kwargsDict | |
) | |
if ( cls != unsupplied ) then cls = ( this.GetClassTitle cls ) else cls = "" | |
format "ERROR :: [%] !-!-!-!-!-! % !-!-!-!-!-!\n" cls ( msg + extraStr ) | |
), | |
fn Critical msg args:#() kwargsDict: cls: = | |
( | |
if ( args.count != 0 ) then | |
( | |
msg = this.format msg args:args | |
) | |
local extraStr = "" | |
if ( kwargsDict != unsupplied ) and ( ClassOf kwargsDict == dotNetObject ) then | |
( | |
extraStr = " " + this.ConcatStrFromHashtable kwargsDict | |
) | |
if ( cls != unsupplied ) then cls = ( this.GetClassTitle cls ) else cls = "" | |
format "Critical :: [%] !-!-!-!-!-! % !-!-!-!-!-!\n" cls ( msg + extraStr ) | |
this.DumpListenerTextToFile ( this.GetLogFilepath() ) | |
throw "LOGGER::CRITICAL" | |
), | |
fn Log msg statement:"LOG" lvl:( ::Enum_LoggerLevel.info ) args:#() kwargsDict: cls: = | |
( | |
local logLevel = this.GetLevelValues lvl | |
if ( logLevel != undefined ) and ( this.level == logLevel[1] ) then | |
( | |
if ( args.count != 0 ) then | |
( | |
msg = this.format msg args:args | |
) | |
local extraStr = "" | |
if ( kwargsDict != unsupplied ) and ( ClassOf kwargsDict == dotNetObject ) then | |
( | |
extraStr = " " + this.ConcatStrFromHashtable kwargsDict | |
) | |
if ( cls != unsupplied ) then cls = ( this.GetClassTitle cls ) else cls = "" | |
format "% :: [%] ========== % ==========\n" statement cls ( msg + extraStr ) | |
) | |
), | |
fn Format msg args:#() = | |
( | |
local str = msg | |
for i = 1 to args.count do | |
( | |
str = SubstituteString str ( "{" + ( i as string ) + "}" ) ( args[i] as string ) | |
) | |
str | |
), | |
fn GetModule = | |
( | |
( GetSourceFileName() ) | |
), | |
fn Help = | |
( | |
::mxs.GetScriptHelp ( GetSourceFileName() ) | |
), | |
private | |
level = ::Enum_LoggerLevel.info, | |
level_val = ( GetProperty ::Enum_LevelVal this.level ), | |
_maxVerbosity = 4, | |
verbosity = 1, | |
fn ConcatStrFromHashtable dict = | |
( | |
local str = StringStream "" | |
local dictKeys = ::_hash.GetDicKeys dict | |
for k in dictKeys do | |
( | |
format "| % :: % " k dict.item[k] to:str | |
) | |
( str as string ) + "|" | |
), | |
fn InvalidLevel lvl = | |
( | |
if ( ClassOf lvl ) == Integer then | |
( | |
lvl = lvl as string | |
this.Error ( lvl + " is not a valid level value, expected one of: " + (#(0, 1, 2) as string )) | |
) | |
else | |
( | |
this.Error ( lvl + " is not a valid level, expected one of: " + (( GetPropNames ::Enum_LoggerLevel ) as string )) | |
) | |
), | |
fn GetLevelValues lvl = | |
( | |
local out = #() | |
if ( ClassOf lvl ) == Integer then | |
( | |
if ( lvl > 2 ) then | |
( | |
this.InvalidLevel lvl | |
return undefined | |
) | |
local prps = ( GetPropNames ::Enum_LevelVal ) | |
prp = ( for p in prps where ( GetProperty ::Enum_LevelVal p ) == lvl collect p )[1] | |
out = #(prp, ( GetProperty ::Enum_LevelVal prp )) | |
) | |
else if ( IsProperty ::Enum_LoggerLevel lvl ) then | |
( | |
local prp = ( GetProperty ::Enum_LoggerLevel lvl ) | |
out = #(prp, ( GetProperty ::Enum_LevelVal prp )) | |
) | |
else | |
( | |
this.InvalidLevel lvl | |
) | |
out | |
), | |
fn GetClassTitle cls = | |
( | |
local str = ( cls as string ) | |
local classTitle = ( trimLeft ( FilterString str " " )[1] "(" ) | |
), | |
fn _init = | |
( | |
this.CleanUpLogs threshold_days:12 -- Delete old log files | |
this.EnableLogging ( this.GetLogFilepath() ) | |
), | |
__init__ = _init() | |
) | |
Logger = Logger() | |
mxs.Using "HashTableMethods" |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment