Add a RollingFileAppender for vision client logs to ease debug in client scope with external log viewer tools

Vision console is not very user friendly for logs analyse in CLIENT scope.
So you can add a RollingFileAppender for all client’s logs produced with code like :

logger = system.util.getLogger("myLogger")
if logger.isDebugEnabled():
	logger.debug("Hello, world!")

(print message are not concerned and stay only in the console)

In order to route the logs to others files in parallel with the vision console, you can add a call

  • to startLogClient() in your client startup script.
  • to stopLogClient() in your client shutdown script.

Now, you can use an external tool like these for windows, to consult the logs : http://www.log-expert.de/ , Bare Metal Software > BareTail - Free tail for Windows or all other tailer tools or log analyse/viewer tools.

Hope this help !

appenderName = "my-vision-logs"

def startLogClient():

   global appenderName

   logDir = "C:/logs"	
   logDir = "e:/log"
   logMaxFileSize = "1 MB"
   logMaxIndex = 10

   logPattern = "%date{yyyy/MM/dd HH:mm:ss.SSS} [%thread] %-5level %logger{35} - %msg%n"	
   
   scope = getScope()	
   try:
   
   	if (scope in ["C","D"]):
   	
   		#https://logback.qos.ch/manual
   		#https://logback.qos.ch/apidocs/index.html
   		#https://stackoverflow.com/questions/47299109/programatically-add-appender-in-logback-slf4j
   		#https://javaetmoi.com/2018/03/configurez-logback-en-java/
   		 
   		from org.slf4j import LoggerFactory
   		from ch.qos.logback.classic import Logger
   		from ch.qos.logback.classic import LoggerContext
   		from ch.qos.logback.classic.encoder import PatternLayoutEncoder
   		from ch.qos.logback.core import FileAppender
   		from ch.qos.logback.core.util import FileSize
   		from ch.qos.logback.core.rolling import RollingFileAppender
   		from ch.qos.logback.core.rolling import FixedWindowRollingPolicy
   		from ch.qos.logback.core.rolling import SizeBasedTriggeringPolicy		
   				
   		loggerContext = LoggerFactory.getILoggerFactory()
   		
   		# to avoid file's conflict if Client and designer launched on the same machine
   		if scope=="C":
   			filename="client-vision"
   		else:
   			filename="designer"
   		
   		logPath = "%s/%s.txt" % (logDir,filename) 
   		logFileNamePattern = "%s/%s_%s.txt" % (logDir,filename,"%i") 
   		
   		#print logPath
   		#print logFileNamePattern
   		
   		rfAppender = RollingFileAppender()
   		rfAppender.setContext(loggerContext)
   		rfAppender.setFile(logPath)
   		rfAppender.setName(appenderName)

   		fwRollingPolicy = FixedWindowRollingPolicy()
   		fwRollingPolicy.setContext(loggerContext)		
   		fwRollingPolicy.setMinIndex(1)
   		fwRollingPolicy.setMaxIndex(logMaxIndex)			
   		fwRollingPolicy.setFileNamePattern(logFileNamePattern)
   		fwRollingPolicy.setParent(rfAppender)
   		fwRollingPolicy.start()

   		triggeringPolicy = SizeBasedTriggeringPolicy()
   		triggeringPolicy.setMaxFileSize(logMaxFileSize)
   		triggeringPolicy.start()

   		encoder = PatternLayoutEncoder()
   		encoder.setContext(loggerContext)
   		encoder.setPattern(logPattern)
   		encoder.start()

   		rfAppender.setEncoder(encoder)
   		rfAppender.setRollingPolicy(fwRollingPolicy)
   		rfAppender.setTriggeringPolicy(triggeringPolicy)
   		rfAppender.start()
   		
   		# for adding the appender for all client loggers
   		logbackLogger =	LoggerFactory.getLogger(Logger.ROOT_LOGGER_NAME)

   		# In case this function is called multiple times
   		appender = logbackLogger.getAppender(appenderName)
   		if appender != None:
   			logbackLogger.detachAppender(appenderName)
   		
   		logbackLogger.addAppender(rfAppender)
   except:
   	print "error startLogClient()"
   		
def getScope():
   from com.inductiveautomation.ignition.common.model import ApplicationScope 
   try:
   	scope = ApplicationScope.getGlobalScope()
   	if ApplicationScope.isGateway(scope):
   		# Scope Gateway
   		return "G"
   	elif ApplicationScope.isClient(scope):
   		# Scope Client
   		return "C"
   	elif ApplicationScope.isDesigner(scope):
   		# Scope Client
   		return "D"
   	else:
   		return "?"
   except:
   	return "?"		
   	
def stopLogClient():

   global appenderName

   scope = getScope()
   if (scope in ["C","D"]):
   	from org.slf4j import LoggerFactory
   	from ch.qos.logback.classic import Logger	
   	
   	logbackLogger =	LoggerFactory.getLogger(Logger.ROOT_LOGGER_NAME)
   	appender = logbackLogger.getAppender(appenderName) 
   	if appender != None:
   		logbackLogger.detachAppender(appenderName)
6 Likes

Thanks for sharing.

I’ve doing some tests,I want to capture some logs but in the gateway.

Is there a way to prevent an appender added here to log to the wrapper.log file?

gateway logs and clients logs depend of the execution scope

So I tweaked the script to include the gateway scope and I am getting what I need, but I noticed that it also writes to the wrapper.log, which would duplicate the data.

We have an application were we want to keep separate log files for different processes, this solves our requirement, but it will push a lot of data to the wrapper.log, I would prefer to avoid that to keep the wrapper.log as clean as possible.