I got this working and thought I'd follow up with what I did in case anyone
else needs this sort of thing.
I used SiftingAppender pretty much as shown in any of the examples one can find
online. I put the following logback.xml in my sling.home, and pointed the OSGI
configuration variable org.apache.sling.commons.log.configurationFile to it:
<configuration>
<appender name="SIFT" class="ch.qos.logback.classic.sift.SiftingAppender">
<discriminator>
<key>jobId</key>
<defaultValue>/var/log/sling/error.log</defaultValue>
</discriminator>
<sift>
<appender name="FILE-${jobId}" class="ch.qos.logback.core.FileAppender">
<file>${logPath}</file>
<layout class="ch.qos.logback.classic.PatternLayout">
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} -
%msg%n</pattern>
</layout>
</appender>
</sift>
</appender>
<root level="info">
<appender-ref ref="SIFT" />
</root>
</configuration>
An abstract wrapper implementation of JobExecutor sets up the MDC to conform to
what's expected in the logback.xml, and provides a few other niceties. Replace
the SlingResourceProvider/StorageNode stuff with whatever you use to access
files and Sling nodes; it's just a simple abstraction layer that we happen to
be using.
package com.xyz.content.sling.processor;
import java.io.BufferedReader;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import org.apache.sling.api.resource.LoginException;
import org.apache.sling.api.resource.ResourceResolver;
import org.apache.sling.api.resource.ResourceResolverFactory;
import org.apache.sling.event.jobs.Job;
import org.apache.sling.event.jobs.consumer.JobExecutionContext;
import org.apache.sling.event.jobs.consumer.JobExecutionResult;
import org.apache.sling.event.jobs.consumer.JobExecutor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.MDC;
import com.xyz.content.sling.storage.SlingResourceProvider;
import com.xyz.storage.StorageNode;
/**
* Sling job executor with build job logging.
*
* This class wraps build processing with job-specific log management.
* Job submitters need to configure the following job properties
* prior to submitting a job:
* * jobId - A symbolic identifier for the job. This value must be
* unique throughout the life of the job. Subclasses
* may use this value as a job name for submitting
* and monitoring SLURM jobs.
* * logPath - The pathname to the build log file.
* * damLogPath - If specified, the DAM resource to which
* the log output should be copied upon build completion.
* * clearLog - An option boolean parameter which, when true,
* removes the build log file if it exists prior
* to commencing the build.
*
* @author john
*
*/
public abstract class BuildJobExecutor implements JobExecutor {
private static Logger LOG = LoggerFactory.getLogger(BuildJobExecutor.class);
/**
* Retrieve a resource resolver factory for build processing.
*
* @return the resource resolver factory
*/
protected abstract ResourceResolverFactory getResolverFactory();
/**
* Subclass-specific build processing method.
*
* @param job
* @param context
* @param resolver
* @return the result of build processing
*/
protected abstract JobExecutionResult build(Job job, JobExecutionContext
context, ResourceResolver resolver);
@Override
public JobExecutionResult process(Job job, JobExecutionContext context) {
//
// Prepare for the log directory and file.
//
final String jobId = job.getProperty("jobId", String.class);
final Path logPath = Paths.get(job.getProperty("logPath",
String.class));
final Path logParentPath = logPath.getParent();
if (logParentPath != null) {
try {
Files.createDirectories(logParentPath);
}
catch (final IOException e) {
return handleError(context, "Unable to create log directory " +
logParentPath);
}
}
if (Boolean.TRUE.equals(job.getProperty("resetLog"))) {
try {
Files.deleteIfExists(logPath);
}
catch (final IOException e) {
return handleError(context, "Unable to clear log file " +
logPath);
}
}
LOG.info("Starting build job with ID " + jobId);
ResourceResolver resolver;
try {
resolver = getResolverFactory().getServiceResourceResolver(null);
}
catch (final LoginException e) {
return handleError(context, "Unable get build job resource
resolver, check cbservice user configuration.");
}
//
// Perform the build operation. Logging to the job-specific log file
starts here.
//
MDC.put("jobId", jobId);
MDC.put("logPath", logPath.toString());
try {
final JobExecutionResult result = build(job, context, resolver);
if (job.getProperty("damLogPath") != null) {
final Path damLogPath = Paths.get(job.getProperty("damLogPath",
String.class));
saveLog(resolver, logPath, damLogPath);
}
return result;
}
catch (final Throwable e) {
LOG.error("Build job failed with an exception.", e);
return handleError(context, "Build job failed with an exception: "
+ e.getMessage());
}
finally {
MDC.remove("jobId");
MDC.remove("logPath");
resolver.close();
}
}
private JobExecutionResult handleError(JobExecutionContext context, String
message) {
LOG.error(message);
return context.result().message(message).cancelled();
}
private void saveLog(ResourceResolver resolver, Path logPath, Path
damLogPath) {
try (BufferedReader reader = Files.newBufferedReader(logPath,
StandardCharsets.UTF_8)) {
final SlingResourceProvider storageProvider = new
SlingResourceProvider();
storageProvider.setResolver(resolver);
final StorageNode damLogNode = storageProvider.get(damLogPath);
damLogNode.copyFromReader(reader, StandardCharsets.UTF_8);
resolver.commit();
}
catch (final IOException e) {
LOG.error("Unable to move build log " + logPath + " to DAM resource
" + damLogPath, e);
}
}
}
________________________________
From: Robert Munteanu <[email protected]>
Sent: Friday, September 8, 2017 1:13:02 AM
To: [email protected]
Subject: Re: Directing Sling job logging output to separate files?
Hi John,
On Fri, 2017-09-08 at 05:28 +0000, John Logan wrote:
> Hi,
>
>
> I'm using the Sling job manager to handle some long running tasks,
> and would like to direct the log output for each job to its own file
> at a job-specific path. Is there a straightforward way to achieve
> this?
If your jobs use separate loggers, you can achieve that either by:
- manually creating loggers and appenders via http://localhost:8080/sys
tem/console/slinglog/
- adding specific loggers/appenders to the provisioning model
There might be a way of adding those at runtime using the logback APIs,
but I haven't tried it before.
Robert