/*
 * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
 * or more contributor license agreements. Licensed under the Elastic License
 * 2.0 and the Server Side Public License, v 1; you may not use this file except
 * in compliance with, at your election, the Elastic License 2.0 or the Server
 * Side Public License, v 1.
 */

package org.elasticsearch.common.logging;

import org.apache.logging.log4j.Level;
import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.Marker;
import org.apache.logging.log4j.MarkerManager;
import org.apache.logging.log4j.message.Message;
import org.apache.logging.log4j.spi.ExtendedLogger;
import org.apache.logging.log4j.spi.ExtendedLoggerWrapper;

import java.util.WeakHashMap;

/**
 * A logger that prefixes all messages with a fixed prefix specified during construction. The prefix mechanism uses the marker construct, so
 * for the prefixes to appear, the logging layout pattern must include the marker in its pattern.
 */
class PrefixLogger extends ExtendedLoggerWrapper {

    /*
     * We can not use the built-in Marker tracking (MarkerManager) because the MarkerManager holds a permanent reference to the marker;
     * however, we have transient markers from index-level and shard-level components so this would effectively be a memory leak. Since we
     * can not tie into the lifecycle of these components, we have to use a mechanism that enables garbage collection of such markers when
     * they are no longer in use.
     */
    private static final WeakHashMap<String, Marker> markers = new WeakHashMap<>();

    /**
     * Return the size of the cached markers. This size can vary as markers are cached but collected during GC activity when a given prefix
     * is no longer in use.
     *
     * @return the size of the cached markers
     */
    static int markersSize() {
        return markers.size();
    }

    /**
     * The marker for this prefix logger.
     */
    private final Marker marker;

    /**
     * Obtain the prefix for this prefix logger. This can be used to create a logger with the same prefix as this one.
     *
     * @return the prefix
     */
    public String prefix() {
        return marker.getName();
    }

    /**
     * Construct a prefix logger with the specified name and prefix.
     *
     * @param logger the extended logger to wrap
     * @param prefix the prefix for this prefix logger
     */
    PrefixLogger(final Logger logger, final String prefix) {
        super((ExtendedLogger) logger, logger.getName(), null);

        if (prefix == null || prefix.isEmpty()) {
            throw new IllegalArgumentException("if you don't need a prefix then use a regular logger");
        }
        final Marker actualMarker;
        // markers is not thread-safe, so we synchronize access
        synchronized (markers) {
            final Marker maybeMarker = markers.get(prefix);
            if (maybeMarker == null) {
                actualMarker = new MarkerManager.Log4jMarker(prefix);
                /*
                 * We must create a new instance here as otherwise the marker will hold a reference to the key in the weak hash map; as
                 * those references are held strongly, this would give a strong reference back to the key preventing them from ever being
                 * collected. This also guarantees that no other strong reference can be held to the prefix anywhere.
                 */
                // noinspection RedundantStringConstructorCall
                markers.put(new String(prefix), actualMarker);
            } else {
                actualMarker = maybeMarker;
            }
        }
        this.marker = actualMarker;
    }

    @Override
    public void logMessage(final String fqcn, final Level level, final Marker marker, final Message message, final Throwable t) {
        assert marker == null;
        super.logMessage(fqcn, level, this.marker, message, t);
    }

}
