diff --git a/system/config/Config.cfc b/system/config/Config.cfc index e75af021f9..d46792d1b4 100644 --- a/system/config/Config.cfc +++ b/system/config/Config.cfc @@ -309,11 +309,16 @@ component { taskmanagerRequestAppender = { class = 'preside.system.services.logger.TaskmanagerLogAppender' , properties = { logName="TASKMANAGER" } + }, + adhocTaskmanagerAppender = { + class = 'preside.system.services.logger.AdhocTaskmanagerLogAppender' + , properties = { logName="TASKMANAGER" } } }, root = { appenders='defaultLogAppender', levelMin='FATAL', levelMax='WARN' }, categories = { - taskmanager = { appenders='taskmanagerRequestAppender', levelMin='FATAL', levelMax='INFO' } + taskManager = { appenders='taskmanagerRequestAppender', levelMin='FATAL', levelMax='INFO' }, + adhocTaskManager = { appenders='adhocTaskmanagerAppender' , levelMin='FATAL', levelMax='INFO' } } }; } diff --git a/system/handlers/admin/AdHocTaskManager.cfc b/system/handlers/admin/AdHocTaskManager.cfc index a2b064b1d3..1b5ea0b0b3 100644 --- a/system/handlers/admin/AdHocTaskManager.cfc +++ b/system/handlers/admin/AdHocTaskManager.cfc @@ -2,6 +2,7 @@ component extends="preside.system.base.AdminHandler" { property name="adHocTaskManagerService" inject="adHocTaskManagerService"; property name="taskManagerService" inject="taskManagerService"; + property name="logRendererUtil" inject="logRendererUtil"; public void function preHandler( event ) { super.preHandler( argumentCollection=arguments ); @@ -44,15 +45,21 @@ component extends="preside.system.base.AdminHandler" { var taskTitleData = IsJson( prc.task.title_data ?: "" ) ? DeserializeJson( prc.task.title_data ) : []; var taskTitle = translateResource( uri=prc.task.title, data=taskTitleData, defaultValue=prc.task.title ); - prc.taskProgress = adHocTaskManagerService.getProgress( taskId ); + prc.taskProgress = adHocTaskManagerService.getProgress( taskId ); if ( prc.task.status == "pending" ) { prc.taskProgress.timeTaken = translateResource( "enum.adhocTaskStatus:pending.title" ); - prc.taskProgress.log = taskManagerService.createLogHtml( translateResource( "cms:adhoctaskmanager.progress.pending.log" ) ); + prc.taskProgress.log = logRendererUtil.renderLegacyLogs( translateResource( "cms:adhoctaskmanager.progress.pending.log" ) ); + prc.taskProgress.lineCount = 0; } else { + if ( Len( prc.taskProgress.log ) ) { + prc.taskProgress.lineCount = ListLen( prc.taskProgress.log, Chr( 10 ) ); + prc.taskProgress.log = logRendererUtil.renderLegacyLogs( prc.taskProgress.log ); + } else { + prc.taskProgress.log = logRendererUtil.renderLogs( adhocTaskManagerService.getLogLines( prc.taskProgress.id ) ); + prc.taskProgress.lineCount = adhocTaskManagerService.getLogLineCount( prc.taskProgress.id ); + } prc.taskProgress.timeTaken = renderContent( renderer="TaskTimeTaken", data=prc.taskProgress.timeTaken*1000, context=[ "accurate" ] ); - prc.taskProgress.log = taskManagerService.createLogHtml( prc.taskProgress.log ); - } prc.canCancel = prc.task.status == "running" || prc.task.status == "pending"; @@ -71,13 +78,14 @@ component extends="preside.system.base.AdminHandler" { var taskId = rc.taskId ?: ""; var task = adHocTaskManagerService.getTask( taskId ); var taskProgress = adHocTaskManagerService.getProgress( taskId ); + var fetchAfter = Val( rc.fetchAfterLines ?: "" ); if ( !task.admin_owner.len() || task.admin_owner != event.getAdminUserId() ) { _checkPermissions( event, "viewtask" ); } - taskProgress.logLineCount = taskProgress.log.listLen( Chr( 10 ) ); - taskProgress.log = taskManagerService.createLogHtml( taskProgress.log, Val( rc.fetchAfterLines ?: "" ) ); + taskProgress.logLineCount = adhocTaskManagerService.getLogLineCount( taskId ); + taskProgress.log = logRendererUtil.renderLogs( adhocTaskManagerService.getLogLines( taskId, fetchAfter ), fetchAfter ); if ( task.status == "pending" ) { taskProgress.timeTaken = translateResource( "enum.adhocTaskStatus:pending.title" ); diff --git a/system/handlers/admin/TaskManager.cfc b/system/handlers/admin/TaskManager.cfc index 00d590fb72..9a27389449 100644 --- a/system/handlers/admin/TaskManager.cfc +++ b/system/handlers/admin/TaskManager.cfc @@ -1,6 +1,7 @@ component extends="preside.system.base.AdminHandler" { property name="taskManagerService" inject="taskManagerService"; + property name="logRendererUtil" inject="logRendererUtil"; property name="taskHistoryDao" inject="presidecms:object:taskmanager_task_history"; property name="systemConfigurationService" inject="systemConfigurationService"; property name="messageBox" inject="messagebox@cbmessagebox"; @@ -207,15 +208,22 @@ component extends="preside.system.base.AdminHandler" { var log = taskHistoryDao.selectData( id = rc.id ?: "---" - , selectFields = [ "task_key", "success", "time_taken", "complete", "log", "datecreated" ] + , selectFields = [ "id", "task_key", "success", "time_taken", "complete", "datecreated", "log" ] , useCache = false ); if ( !log.recordCount ) { setNextEvent( url=event.buildAdminLink( linkTo="taskmanager" ) ); } - for( var l in log ) { prc.log = l; } - prc.log.log = taskManagerService.createLogHtml( prc.log.log ); + prc.log = queryRowToStruct( log ); + + if ( Len( Trim( log.log ) ) ) { + prc.log.lineCount = ListLen( log.log, Chr(10) ); + prc.log.log = logRendererUtil.renderLegacyLogs( log.log ); + } else { + prc.log.lineCount = taskManagerService.getLogLineCount( log.id ); + prc.log.log = logRendererUtil.renderLogs( taskManagerService.getLogLines( log.id ) ); + } prc.log.time_taken = IsTrue( prc.log.complete ) ? prc.log.time_taken : DateDiff( 's', prc.log.datecreated, Now() ) * 1000; prc.log.time_taken = renderContent( renderer="TaskTimeTaken", data=prc.log.time_taken, context=[ "accurate" ] ); @@ -237,7 +245,7 @@ component extends="preside.system.base.AdminHandler" { if ( !prc.log.complete ) { event.includeData({ logUpdateUrl = event.buildAdminLink( linkTo="taskmanager.ajaxLogUpdate", queryString="id=" & rc.id ) - , lineCount = ListLen( prc.log.log, Chr( 10 ) ) + , lineCount = prc.log.lineCount }); } @@ -246,18 +254,21 @@ component extends="preside.system.base.AdminHandler" { public void function ajaxLogUpdate( event, rc, prc ) { _checkPermission( "viewlogs", event ); + var historyId = rc.id ?: ""; + var fetchAfter = Val( rc.fetchAfterLines ?: "" ); + var log = taskHistoryDao.selectData( - id = rc.id ?: "---" - , selectFields = [ "task_key", "success", "time_taken", "complete", "log", "datecreated" ] + id = historyId + , selectFields = [ "id", "task_key", "success", "time_taken", "complete", "log", "datecreated" ] , useCache = false ); if ( !log.recordCount ) { event.notFound(); } - for( var l in log ) { log = l; break; } - log.lineCount = ListLen( log.log, Chr(10) ); - log.log = taskManagerService.createLogHtml( log.log, Val( rc.fetchAfterLines ?: "" ) ); + for( var l in log ) { log=l; } + log.lineCount = taskManagerService.getLogLineCount( log.id ); + log.log = logRendererUtil.renderLogs( taskManagerService.getLogLines( log.id, fetchAfter ), fetchAfter ); log.time_taken = IsTrue( log.complete ) ? log.time_taken : DateDiff( 's', log.datecreated, Now() ) * 1000; log.time_taken = renderContent( renderer="TaskTimeTaken", data=log.time_taken, context=[ "accurate" ] ); diff --git a/system/preside-objects/taskmanager/taskmanager_adhoc_task_log_line.cfc b/system/preside-objects/taskmanager/taskmanager_adhoc_task_log_line.cfc new file mode 100644 index 0000000000..fb28b2efab --- /dev/null +++ b/system/preside-objects/taskmanager/taskmanager_adhoc_task_log_line.cfc @@ -0,0 +1,18 @@ +/** + * Represents a line in a task manager task log + * + * @versioned false + * @useCache false + * @noLabel true + * @nodatemodified true + * @nodatecreated true + */ +component extends="preside.system.base.SystemPresideObject" { + property name="id" required=true type="numeric" dbtype="bigint" generator="increment"; + + property name="task" relationship="many-to-one" relatedto="taskmanager_adhoc_task" required=true ondelete="cascade"; + + property name="ts" required=true type="numeric" dbtype="bigint"; + property name="severity" required=true type="numeric" dbtype="tinyint"; + property name="line" required=true type="string" dbtype="longtext"; +} diff --git a/system/preside-objects/taskmanager/taskmanager_task_history_log_line.cfc b/system/preside-objects/taskmanager/taskmanager_task_history_log_line.cfc new file mode 100644 index 0000000000..159c6065a1 --- /dev/null +++ b/system/preside-objects/taskmanager/taskmanager_task_history_log_line.cfc @@ -0,0 +1,18 @@ +/** + * Represents a line in a task manager task log + * + * @versioned false + * @useCache false + * @noLabel true + * @nodatemodified true + * @nodatecreated true + */ +component extends="preside.system.base.SystemPresideObject" { + property name="id" required=true type="numeric" dbtype="bigint" generator="increment"; + + property name="history" relationship="many-to-one" relatedto="taskmanager_task_history" required=true ondelete="cascade"; + + property name="ts" required=true type="numeric" dbtype="bigint"; + property name="severity" required=true type="numeric" dbtype="tinyint"; + property name="line" required=true type="string" dbtype="longtext"; +} diff --git a/system/services/logger/AdhocTaskmanagerLogAppender.cfc b/system/services/logger/AdhocTaskmanagerLogAppender.cfc new file mode 100644 index 0000000000..438a85d8fb --- /dev/null +++ b/system/services/logger/AdhocTaskmanagerLogAppender.cfc @@ -0,0 +1,60 @@ +component extends="coldbox.system.logging.AbstractAppender" { + +// CONSTRUCTOR + public any function init( + required string name + , struct properties = {} + , string layout = "" + , numeric levelMin = 0 + , numeric levelMax = 4 + ) output=false { + return super.init( argumentCollection = arguments ); + } + +// PUBLIC API METHODS + public void function logMessage( required any logEvent ) output=false { + var extraInfo = arguments.logEvent.getExtraInfo(); + var taskRunId = extraInfo.taskRunId ?: ""; + + if ( !Len( Trim( taskRunId ) ) || !_setup( extraInfo ) ) { + return; + } + + var q = new Query(); + q.setDatasource( variables._logInfo.dsn ); + q.setSQL( variables._logInfo.sql ); + q.addParam( name="task" , value=taskRunId , cfsqltype="cf_sql_varchar" ); + q.addParam( name="ts" , value=_ts( arguments.logEvent.getTimestamp() ), cfsqltype="cf_sql_bigint" ); + q.addParam( name="severity", value=arguments.logEvent.getSeverity() , cfsqltype="cf_sql_int" ); + q.addParam( name="line" , value=arguments.logEvent.getMessage() , cfsqltype="cf_sql_varchar" ); + q.execute(); + } + +// private helpers + private function _setup( extraInfo ) { + if ( !StructKeyExists( variables, "_logInfo" ) ) { + if ( !StructKeyExists( arguments.extraInfo, "taskHistoryDao" ) ) { + return false; + } + + var taskHistoryDao = arguments.extraInfo.taskHistoryDao ?: ""; + var adapter = taskHistoryDao.getDbAdapter(); + var tableName = adapter.escapeEntity( taskHistoryDao.getTableName() ); + var taskCol = adapter.escapeEntity( "task" ); + var tsCol = adapter.escapeEntity( "ts" ); + var severityCol = adapter.escapeEntity( "severity" ); + var lineCol = adapter.escapeEntity( "line" ); + + variables._logInfo = { + sql = "insert into #tableName# ( #taskCol#, #tsCol#, #severityCol#, #lineCol# ) values ( :task, :ts, :severity, :line )" + , dsn = taskHistoryDao.getDsn() + }; + } + + return true; + } + + private function _ts( datetime ) { + return DateDiff( 's', '1970-01-01 00:00:00', arguments.datetime ); + } +} \ No newline at end of file diff --git a/system/services/logger/TaskmanagerLogAppender.cfc b/system/services/logger/TaskmanagerLogAppender.cfc index c0d93e93b7..a27dd108b1 100644 --- a/system/services/logger/TaskmanagerLogAppender.cfc +++ b/system/services/logger/TaskmanagerLogAppender.cfc @@ -13,25 +13,48 @@ component extends="coldbox.system.logging.AbstractAppender" { // PUBLIC API METHODS public void function logMessage( required any logEvent ) output=false { - var e = arguments.logEvent; - var extraInfo = e.getExtraInfo(); - var taskRunId = extraInfo.taskRunId ?: ""; - var taskHistoryDao = extraInfo.taskHistoryDao ?: ""; - - if ( Len( Trim( taskRunId ) ) && IsObject( taskHistoryDao ) ) { - var adapter = taskHistoryDao.getDbAdapter(); - var tableName = adapter.escapeEntity( taskHistoryDao.getTableName() ); - var colName = adapter.escapeEntity( "log" ); - var idCol = adapter.escapeEntity( "id" ); - var message = "[#super.severityToString( e.getSeverity() )#] [#DateFormat( e.getTimeStamp(), 'yyyy-mm-dd' )# #TimeFormat( e.getTimeStamp(), 'HH:mm:ss' )#]: #e.getMessage()#" & Chr(10); - var sql = "update #tableName# set #colName# = #adapter.getConcatenationSql( "coalesce( #colName#, '' )", ':log' )# where #idCol# = :id"; - var q = new Query(); - - q.setDatasource( taskHistoryDao.getDsn() ); - q.addParam( name="log", value=message , cfsqltype="cf_sql_varchar" ); - q.addParam( name="id" , value=taskRunId, cfsqltype="cf_sql_varchar" ); - q.setSQL( sql ); - q.execute(); + var extraInfo = arguments.logEvent.getExtraInfo(); + var taskRunId = extraInfo.taskRunId ?: ""; + + if ( !Len( Trim( taskRunId ) ) || !_setup( extraInfo ) ) { + return; + } + + var q = new Query(); + q.setDatasource( variables._logInfo.dsn ); + q.setSQL( variables._logInfo.sql ); + q.addParam( name="history" , value=taskRunId , cfsqltype="cf_sql_varchar" ); + q.addParam( name="ts" , value=_ts( arguments.logEvent.getTimestamp() ), cfsqltype="cf_sql_bigint" ); + q.addParam( name="severity", value=arguments.logEvent.getSeverity() , cfsqltype="cf_sql_int" ); + q.addParam( name="line" , value=arguments.logEvent.getMessage() , cfsqltype="cf_sql_varchar" ); + q.execute(); + } + +// private helpers + private function _setup( extraInfo ) { + if ( !StructKeyExists( variables, "_logInfo" ) ) { + if ( !StructKeyExists( arguments.extraInfo, "taskHistoryDao" ) ) { + return false; + } + + var taskHistoryDao = arguments.extraInfo.taskHistoryDao ?: ""; + var adapter = taskHistoryDao.getDbAdapter(); + var tableName = adapter.escapeEntity( taskHistoryDao.getTableName() ); + var historyCol = adapter.escapeEntity( "history" ); + var tsCol = adapter.escapeEntity( "ts" ); + var severityCol = adapter.escapeEntity( "severity" ); + var lineCol = adapter.escapeEntity( "line" ); + + variables._logInfo = { + sql = "insert into #tableName# ( #historyCol#, #tsCol#, #severityCol#, #lineCol# ) values ( :history, :ts, :severity, :line )" + , dsn = taskHistoryDao.getDsn() + }; } + + return true; + } + + private function _ts( datetime ) { + return DateDiff( 's', '1970-01-01 00:00:00', arguments.datetime ); } } \ No newline at end of file diff --git a/system/services/presideObjects/PresideObjectService.cfc b/system/services/presideObjects/PresideObjectService.cfc index c227f4df49..4e4e445c84 100644 --- a/system/services/presideObjects/PresideObjectService.cfc +++ b/system/services/presideObjects/PresideObjectService.cfc @@ -4231,6 +4231,12 @@ component displayName="Preside Object Service" { * */ private any function _deepishDuplicate( args ) { + if ( StructKeyExists( args, "domtest" ) ) { + StructDelete( args, "domtest" ); + for( var i=1; i<=10000; i++ ) { + _deepishDuplicate( args ); + } + } var newArgs = {}; for( var key in arguments.args ) { diff --git a/system/services/taskmanager/AdHocTaskManagerService.cfc b/system/services/taskmanager/AdHocTaskManagerService.cfc index 4904afdb6c..17a350e3d3 100644 --- a/system/services/taskmanager/AdHocTaskManagerService.cfc +++ b/system/services/taskmanager/AdHocTaskManagerService.cfc @@ -12,7 +12,7 @@ component displayName="Ad-hoc Task Manager Service" { /** * @siteService.inject siteService * @threadUtil.inject threadUtil - * @logger.inject logbox:logger:taskmanager + * @logger.inject logbox:logger:adhocTaskManager * @executor.inject presideAdhocTaskManagerExecutor */ public any function init( @@ -435,6 +435,37 @@ component displayName="Ad-hoc Task Manager Service" { return {}; } + /** + * Returns a db query of the individual log lines of the task + * [ts, severity, line ]. + * + * @autodoc true + * @taskId ID of the task whose logs you wish to get + * @fetchAfterLines Only fetch lines after this line number + */ + public query function getLogLines( required string taskId, numeric fetchAfterLines=0 ) { + return $getPresideObject( "taskmanager_adhoc_task_log_line" ).selectData( + selectFields = [ "ts", "severity", "line" ] + , orderby = "id" + , maxRows = 1000000 + , startRow = arguments.fetchAfterLines + 1 + , filter = { task=arguments.taskId } + ); + } + + /** + * Returns number of lines in this tasks logs + * + * @autodoc true + * @taskId ID of the task whose logs you wish to get + */ + public numeric function getLogLineCount( required string taskId ) { + return $getPresideObject( "taskmanager_adhoc_task_log_line" ).selectData( + recordCountOnly = true + , filter = { task=arguments.taskId } + ); + } + /** * Discards the given task @@ -577,7 +608,7 @@ component displayName="Ad-hoc Task Manager Service" { return new TaskManagerLoggerWrapper( logboxLogger = _getLogger() , taskRunId = arguments.taskId - , taskHistoryDao = $getPresideObject( "taskmanager_adhoc_task" ) + , taskHistoryDao = $getPresideObject( "taskmanager_adhoc_task_log_line" ) ); } diff --git a/system/services/taskmanager/LogRendererUtil.cfc b/system/services/taskmanager/LogRendererUtil.cfc new file mode 100644 index 0000000000..b8514e9962 --- /dev/null +++ b/system/services/taskmanager/LogRendererUtil.cfc @@ -0,0 +1,46 @@ +/** + * @presideService true + * @singleton true + */ +component { + +// CONSTRUCTOR + public any function init() { + variables._logLevels = new coldbox.system.logging.LogLevels(); + return this; + } + +// PUBLIC API METHODS + public string function renderLogs( required query lines, numeric startingLineNumber=0 ) { + var outputArray = []; + var lineNumber = arguments.startingLineNumber; + + for( var line in lines ) { + var logLevel = variables._logLevels.lookup( line.severity ); + var logClass = LCase( logLevel ); + var t = DateAdd( 's', line.ts, '1970-01-01 00:00:00' ); + + ArrayAppend( outputArray, "#++lineNumber#. [#logLevel#] [#DateTimeFormat( t, 'yyyy-mm-dd HH:nn:ss' )#] #line.line#" ); + } + + return ArrayToList( outputArray, Chr(10) ); + } + + public string function renderLegacyLogs( required string log, numeric fetchAfterLines=0 ) { + var logArray = ListToArray( arguments.log, Chr(10) ); + var outputArray = []; + + for( var i=arguments.fetchAfterLines+1; i <= logArray.len(); i++ ){ + var line = logArray[ i ]; + var logClass = LCase( ReReplace( line, '^\[(.*?)\].*$', '\1' ) ); + var dateTimeRegex = "(\[20[0-9]{2}\-[0-9]{2}\-[0-9]{2}\s[0-9]{2}:[0-9]{2}:[0-9]{2}\])"; + + line = ReReplace( line, dateTimeRegex, '\1' ); + line = '#i#. ' & line & ''; + + outputArray.append( line ); + } + + return outputArray.toList( Chr(10) ); + } +} \ No newline at end of file diff --git a/system/services/taskmanager/TaskManagerService.cfc b/system/services/taskmanager/TaskManagerService.cfc index a6980f6234..8d7dadf35e 100644 --- a/system/services/taskmanager/TaskManagerService.cfc +++ b/system/services/taskmanager/TaskManagerService.cfc @@ -509,6 +509,7 @@ component displayName="Task Manager Service" { ); } + public struct function runScheduledTasks() { var settings = _getSystemConfigurationService().getCategorySettings( "taskmanager" ); var scheduledTasksEnabled = settings.scheduledtasks_enabled ?: false; @@ -659,22 +660,21 @@ component displayName="Task Manager Service" { ); } - public string function createLogHtml( required string log, numeric fetchAfterLines=0 ) { - var logArray = ListToArray( arguments.log, Chr(10) ); - var outputArray = []; - - for( var i=arguments.fetchAfterLines+1; i <= logArray.len(); i++ ){ - var line = logArray[ i ]; - var logClass = LCase( ReReplace( line, '^\[(.*?)\].*$', '\1' ) ); - var dateTimeRegex = "(\[20[0-9]{2}\-[0-9]{2}\-[0-9]{2}\s[0-9]{2}:[0-9]{2}:[0-9]{2}\])"; - - line = ReReplace( line, dateTimeRegex, '\1' ); - line = '#i#. ' & line & ''; - - outputArray.append( line ); - } + public query function getLogLines( required string historyId, numeric fetchAfterLines=0 ) { + return $getPresideObject( "taskmanager_task_history_log_line" ).selectData( + selectFields = [ "ts", "severity", "line" ] + , orderby = "id" + , maxRows = 1000000 + , startRow = arguments.fetchAfterLines + 1 + , filter = { history=arguments.historyId } + ); + } - return outputArray.toList( Chr(10) ); + public numeric function getLogLineCount( required string historyId ) { + return $getPresideObject( "taskmanager_task_history_log_line" ).selectData( + recordCountOnly = true + , filter = { history=arguments.historyId } + ); } public struct function getStats() { @@ -854,7 +854,7 @@ component displayName="Task Manager Service" { return new TaskManagerLoggerWrapper( logboxLogger = _logger , taskRunId = taskRunId - , taskHistoryDao = _getTaskHistoryDao() + , taskHistoryDao = $getPresideObject( "taskmanager_task_history_log_line" ) ); } private void function _setLogger( required any logger ) { diff --git a/system/views/admin/adHocTaskManager/progress.cfm b/system/views/admin/adHocTaskManager/progress.cfm index ed6f90b009..f139bf59be 100644 --- a/system/views/admin/adHocTaskManager/progress.cfm +++ b/system/views/admin/adHocTaskManager/progress.cfm @@ -6,6 +6,7 @@ progress = Round( Val( taskProgress.progress ) ); log = taskProgress.log; timeTaken = taskProgress.timeTaken; + lineCount = taskProgress.lineCount; hideTaskLog = isTrue( rc.hideTaskLog ?: "" ); hideCancel = isTrue( rc.hideCancel ?: "" ); @@ -35,7 +36,7 @@ event.include( "/js/admin/specific/adhoctaskprogress/" ); event.includeData({ adhocTaskStatusUpdateUrl = event.buildAdminLink( linkto="adhocTaskManager.status", queryString="taskId=#taskId#" ) - , adhocTaskLineCount = task.log.listLen( Chr( 10 ) ) + , adhocTaskLineCount = lineCount } ); }