/* * Copyright 2014 Martin Kouba * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.trimou.handlebars; import static org.trimou.handlebars.OptionsHashKeys.LEVEL; import java.util.List; import java.util.Map; import java.util.Set; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.trimou.util.Arrays; import org.trimou.util.Checker; import org.trimou.util.ImmutableSet; /** * <p> * A simple log helper. There is a special {@link Builder} for convenience. * </p> * * <p> * First register the helper instance: * </p> * <code> * MustacheEngineBuilder.newBuilder().registerHelper("log", LogHelper.builder().setDefaultLevel(Level.DEBUG).build()).build(); * </code> * * <p> * Than use the helper in the template: * </p> * <code> * {{log "Hello"}} * </code> * * <p> * The message need not be a string literal: * </p> * <code> * {{log foo.message}} * </code> * * <p> * You may also override the default log level: * </p> * <code> * {{log "" level="DEBUG"}} * </code> * * <p> * And also use message parameters: * </p> * <code> * {{log "Number of items found: {}" items.size}} * </code> * <p> * Note that the final output will depend on the specified {@link LoggerAdapter} * and underlying logging framework configuration. * </p> * * @author Martin Kouba * @see Level * @see LoggerAdapter */ public class LogHelper extends BasicValueHelper { private static final Logger LOGGER = LoggerFactory .getLogger(LogHelper.class); private final LoggerAdapter adapter; private final Level defaultLevel; private final boolean appendTemplateInfo; /** * * @param adapter * @param defaultLevel * @param appendTemplateInfo * If true, a template name and helper line will be appended to * each log message */ public LogHelper(LoggerAdapter adapter, Level defaultLevel, boolean appendTemplateInfo) { Checker.checkArgumentsNotNull(adapter, defaultLevel); this.defaultLevel = defaultLevel; this.adapter = adapter; this.appendTemplateInfo = appendTemplateInfo; } @Override public void execute(Options options) { String message = options.getParameters().get(0).toString(); if (appendTemplateInfo) { StringBuilder builder = new StringBuilder(message); builder.append(" ["); builder.append(options.getTagInfo().getTemplateName()); builder.append(":"); builder.append(options.getTagInfo().getLine()); builder.append("]"); message = builder.toString(); } adapter.log(getLevel(options.getHash()), message, getMessageParams(options.getParameters())); } @Override protected Set<String> getSupportedHashKeys() { return ImmutableSet.of(LEVEL); } private Level getLevel(Map<String, Object> hash) { if (hash.isEmpty() || !hash.containsKey(LEVEL)) { return defaultLevel; } String customLevel = hash.get(LEVEL).toString(); Level level = Level.parse(customLevel); if (level == null) { LOGGER.warn( "Unsupported level specified: {}, using the default one: {}", customLevel, defaultLevel); level = defaultLevel; } return level; } private Object[] getMessageParams(List<Object> params) { if (params.size() > 1) { return params.subList(1, params.size()).toArray(); } return Arrays.EMPTY_OBJECT_ARRAY; } /** * * @return a new instance of builder */ public static Builder builder() { return new Builder(); } public static class Builder { private Level level; private LoggerAdapter adapter; private boolean appendTemplateInfo = true; /** * If not set, {@link Level#INFO} is used. * * @param level * @return builder */ public Builder setDefaultLevel(Level level) { this.level = level; return this; } /** * If not set, a default adapter is used. * * @param adapter * @return builder */ public Builder setLoggerAdapter(LoggerAdapter adapter) { this.adapter = adapter; return this; } /** * If true, a template name and helper line will be appended to each log * message. * * @param value * @return builder */ public Builder setAppendTemplateInfo(boolean value) { this.appendTemplateInfo = value; return this; } public LogHelper build() { return new LogHelper( adapter != null ? adapter : new Slf4jLoggerAdapter(LogHelper.class.getName()), level != null ? level : Level.INFO, appendTemplateInfo); } } /** * Log level. */ public enum Level { ERROR, WARN, INFO, DEBUG, TRACE; static Level parse(String value) { for (Level level : values()) { if (value.equals(level.toString())) { return level; } } return null; } } /** * Log event adapter. */ public interface LoggerAdapter { /** * * @param level * @param message * @param params */ void log(Level level, String message, Object[] params); } /** * A default adapter implementation for SLF4J. */ public static class Slf4jLoggerAdapter implements LoggerAdapter { private final Logger logger; public Slf4jLoggerAdapter(String name) { this.logger = LoggerFactory.getLogger(name); } @Override public void log(Level level, String message, Object[] params) { switch (level) { case ERROR: logger.error(message, params); break; case WARN: logger.warn(message, params); break; case INFO: logger.info(message, params); break; case DEBUG: logger.debug(message, params); break; case TRACE: logger.trace(message, params); break; default: break; } } } }