how to output logs from Scanners?

What's the proper way for custom Scanners to output log entries for help with debugging/troubleshooting?

 

I see print used in here:

https://github.com/plexinc-plugins/Scanners.bundle/blob/master/Contents/Resources/Movies/Plex%20Movie%20Scanner.py

 

but I don't see the messages in the Plex Scanner log.

 

For now I am using the standard python logging module and write to my own log file, but trying to understand if there's a more preferred way to write log entries that would go to one of the PLEX logs automatically.

 

Thanks!

 

Found some old post about the Scanner doesn't run inside the framework and hence don't have access to Log. I suppose that hasn't changed.

Did found out that the Scanner does have access to the standard python 'logging' module, as well as json, so it's OK to use those and no need to bundle those with the Scanner. (Saw some old posts about bundling those modules with the scanner so I got a bit worried and wanted to make sure it's available to the Scanner on a clean machine. Tested with a clean Windows VM and it worked)

Have a look at my absolute series scanner packaged with my HAMA agent...

   . Agents follows the framework but can still write and read into the agent data folder, but cannot create folders by themselves

   . Scanners are not part of the framework, and from my findings it needs to use absolute path which are OS specific

I did use a log function from within the scanner for development and T/S reasons, which was only done in one another scanner with a file.write function.

I did write this function, and tried to make it generic across os but failed. I use sinology and it puts the logs into "homes/plex" folder by default. If folder rights are amended should works on most OS. Any feedback on that would be nice

### Log function ########################################################################################
def Log(entry, filename='Plex Media Scanner Custom.log'): #need relative path
  #Logging = [ ['Synology',          "/volume1/Plex/Library/Application Support/Plex Media Server/Logs/"],
  #            ['Synology2',         "../../Plex/Library/Application Support/Plex Media Server/Logs/"],
  #            ['Ubuntu-10.04',      "/var/lib/plexmediaserver/Library/Application Support/Plex Media Server/Logs/"],
  #            ['windows Vista/7/8', "C:\\Users\\Default\\AppData\\Local\\Plex Media Server\\Logs\\"],
  #            ['Qnap',              ""] ]
  try: 
    with open(filename, 'a') as file:     #time now.strftime("%Y-%m-%d %H:%M") + " " + datetime.datetime.now() + " " + #doesn't work for timestamps
      #for line in file:
      # if entry in line: break
      #else: ###gets there if "for" wasn't "break", used for unique lines in logging file
      file.write( entry + "
" ) #
 make it readable in notepad under windows directly print line + "
" #for command line execution, output to terminal except: pass

And main function:

### Look for episodes ###################################################################################
def Scan(path, files, mediaList, subdirs, language=None, root=None):

Root scan for OS information that i need to complete the Log function

if path == “”:
Log("================================================================================")
try:
os_uname=""
for string in os.uname(): os_uname += string
Log(“os.uname(): ‘%s’” % os_uname) #(sysname, nodename, release, version, machine) #
Log(“Sys.platform: ‘%s’” % sys.platform) #
Log(“os.name: ‘%s’” % os.name) # ‘posix’, ‘nt’, ‘os2’, ‘ce’, ‘java’, ‘riscos’.)
Log(“os.getcwd: ‘%s’” % os.getcwd()) # Current dir: /volume1/homes/plex
Log(“root folder: ‘%s’” % root if root is not None else “”)
except: pass

This output doesn't work in all Operating Systems so i had trouble writing a generic enough function...

Please let me know of any advance in functionality as i want to have logging working across all OS, and the above should give you a head start

Thank you for sharing. 

One thing you could do is to use environment variables instead of hard coding a path. This is more generic and should save you from needing to write more OS specific code.

I have something like this in my code:

# default PMS data location
LOC_WIN = '%LOCALAPPDATA%\\Plex Media Server'
LOC_MAC = '$HOME/Library/Application Support/Plex Media Server'
LOC_LIN = '$PLEX_HOME/Library/Application Support/Plex Media Server'

Now to get from this to a full file path, you can use os.path.expandvars()

path = os.path.expandvars(LOC_WIN)

You can also use "~" in the value, and to expand this to the user's home directory, you can use os.path.expanduser() which would substitute ~ with the actual user's home folder. I picked environment variable as it's simpler and works for all. The $HOME also works on windows but I used %% to make it more clear to Window users.

It's might be better to simply use Python's logging class than to create your own. It's been well tested and should work across a variety of systems. It also supports being called from multiple threads.

I use something like this:

# setup logging
LOG_FORMAT = '%(asctime)s| %(levelname)-8s| %(message)s'
logging.basicConfig(filename=path, format=LOG_FORMAT, level=logging.DEBUG)

test logs

logging.debug(‘some debug message: %s’, ‘test123’)
logging.info(‘some info message: %d’, 314)
logging.warning(‘some warning message: %s’, ‘testabc’)
logging.error(’%s: some error message: %s’, ‘func’, ‘detail’)
logging.critical(‘some critical message: %s’, ‘highest category’)

I wrote a simple log wrapper that allows you to use the same interface in both the Scanner and the Agent. It uses Log.Info() in Agent that writes to the agent log, and logging.info() in Scanner that writes to your own log.

https://github.com/ai7/sagetv-for-plexmediacenter/blob/master/src/plex/common/sageplex/plexlog.py

the *arg and **kwargs is simply python's way of defining a function that takes a variable number of parameters, and pass those parameters to another function (the real logging function) that also takes a variable number of parameters.

This enables me to write

mylog.debug('some debug message')

in both the scanner and agent code, and it would log to the appropriate location.

Note, this wrapper class doesn't actually create the logging object itself. This is done in the scanner. This is simply a wrapper for the actual log message call.

thanks a lot for the feedback, i tried to assist and ended up learning more in the process...

'$PLEX_HOME/Library/Application Support/Plex Media Server' however translates for me to '/volume2/homes/plex/$PLEX_HOME/Library/Application Support/Plex Media Server/Plex Media Scanner Custom.log' so reverted to home directory instead.

trying to support subdirectories... my log list the right serie name but all gets grouped under the same serie with parent folder's name...

Thanks for the code, should be working across platforms now hopefully

Thanks to your code on https://github.com/ai7/sagetv-for-plexmediacenter/blob/master/src/plex/common/sageplex/config.py i got it working, and just re-coded to put my absolute series scanner logs inside Plex logs folder. it supports windows notepad readable logs from all platforms

### setup logging https://docs.python.org/2/library/logging.html ###  #logging.debug/info/warning/error/critical('some critical message: %s', 'highest category')
try:      platform = sys.platform.lower()                                                     # sys.platform: win32 | darwin | linux2, 
except:                                                                                       #
  try:    platform = Platform.OS.lower()                                                      # Platform.OS:  Windows, MacOSX, or Linux #  
  except: platform = ""                                                                       #

LOG_FILE = ‘Plex Media Scanner (custom ASS).log’
LOG_FORMAT = ‘%(asctime)s| %(levelname)-8s| %(message)s’
LOG_WIN = [ ‘%LOCALAPPDATA%\Plex Media Server\Logs’, # Windows 8
‘%USERPROFILE%\Local Settings\Application Data\Plex Media Server\Logs’ ] # ?
LOG_MAC = [ ‘$HOME/Library/Application Support/Plex Media Server/Logs’ ]
LOG_LIN = [ ‘$PLEX_HOME/Library/Application Support/Plex Media Server/Logs’, #Linux
‘/var/lib/plexmediaserver/Library/Application Support/Plex Media Server/Logs’, #Debian, Fedora, CentOS, Ubuntu
‘/usr/local/plexdata/Plex Media Server/Logs’, #FreeBSD
‘/usr/pbi/plexmediaserver-amd64/plexdata/Plex Media Server/Logs’, #FreeNAS
‘/c/.plex/Library/Application Support/Plex Media Server/Logs’, #ReadyNAS
‘/share/MD0_DATA/.qpkg/PlexMediaServer/Library/Plex Media Server/Logs’, #QNAP
‘/volume1/Plex/Library/Application Support/Plex Media Server/Logs’, #Synology, Asustor
‘/volume2/Plex/Library/Application Support/Plex Media Server/Logs’ ] #Synology, if migrated a second raid volume as unique volume in new box
LOG_WTF = [ ‘$HOME’ ] #home folder as backup “C:\users\User.Machine” in windows 8, “users\Plex” on synology

if (platform == ‘win32’ or platform == ‘windows’): LOG_PATHS = LOG_WIN
elif (platform == ‘darwin’ or platform == ‘macosx’): LOG_PATHS = LOG_MAC
elif ‘linux’ in platform: LOG_PATHS = LOG_LIN
else: LOG_PATHS = LOG_WTF
for folder in LOG_PATHS:
LOG_PATH = os.path.expandvars(folder)
if os.path.isdir(LOG_PATH): break
else: LOG_PATH = os.path.expanduser(’~’)
logging.basicConfig(filename=os.path.join(LOG_PATH, LOG_FILE), format=LOG_FORMAT, level=logging.DEBUG)

Log function

def Log(entry, filename=‘Plex Media Scanner Custom.log’): #need relative path
logging.info(entry + "
" if (platform == ‘win32’ or platform == ‘windows’) else entry + "
") # allow linux to output windows notepad comp line feeds but windows plex server add additional line feed with this setting
print entry # when ran from console