/*
 * Copyright 2015-2020 Real Logic Limited., Monotonic Ltd.
 *
 * 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
 *
 * https://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 uk.co.real_logic.artio;


import org.agrona.AsciiSequenceView;
import org.agrona.DirectBuffer;
import org.agrona.concurrent.UnsafeBuffer;
import uk.co.real_logic.artio.AbstractDebugAppender.ThreadLocalAppender;
import uk.co.real_logic.artio.messages.*;
import uk.co.real_logic.artio.util.CharFormatter;

import java.nio.ByteBuffer;
import java.util.Iterator;
import java.util.ServiceLoader;
import java.util.function.Consumer;

import static uk.co.real_logic.artio.CommonConfiguration.*;

/**
 * A logger purely for debug data. Not optimised for high performance logging, but all logging calls must be removable
 * by the optimiser.
 */
public final class DebugLogger
{
    private static final AbstractDebugAppender APPENDER;
    private static final ThreadLocal<ThreadLocalLogger> THREAD_LOCAL = ThreadLocal.withInitial(ThreadLocalLogger::new);

    static
    {
        final ServiceLoader<AbstractDebugAppender> loader = ServiceLoader.load(AbstractDebugAppender.class);
        final Iterator<AbstractDebugAppender> it = loader.iterator();
        if (it.hasNext())
        {
            APPENDER = it.next();
        }
        else
        {
            APPENDER = new PrintingDebugAppender();
        }
    }

    public static void log(
        final LogTag tag,
        final CharFormatter formatter)
    {
        if (isEnabled(tag))
        {
            THREAD_LOCAL.get().log(tag, formatter);
        }
    }

    public static void log(
        final LogTag tag,
        final CharFormatter formatter,
        final int value,
        final DirectBuffer buffer,
        final int offset,
        final int length)
    {
        if (isEnabled(tag))
        {
            formatter.clear().with(value);
            THREAD_LOCAL.get().log(tag, formatter, buffer, offset, length);
        }
    }

    public static void log(
        final LogTag tag,
        final CharFormatter formatter,
        final long first,
        final String second,
        final DirectBuffer buffer,
        final int offset,
        final int length)
    {
        if (isEnabled(tag))
        {
            formatter.clear().with(first).with(second);
            THREAD_LOCAL.get().log(tag, formatter, buffer, offset, length);
        }
    }

    public static void log(
        final LogTag tag,
        final CharFormatter formatter,
        final DirectBuffer buffer,
        final int offset,
        final int length)
    {
        if (isEnabled(tag))
        {
            THREAD_LOCAL.get().log(tag, formatter, buffer, offset, length);
        }
    }

    public static void logSbeMessage(
        final LogTag tag,
        final RedactSequenceUpdateEncoder encoder)
    {
        if (isEnabled(tag))
        {
            THREAD_LOCAL.get().logSbeMessage(tag, encoder);
        }
    }

    public static void logSbeMessage(
        final LogTag tag,
        final ManageSessionEncoder encoder)
    {
        if (isEnabled(tag))
        {
            THREAD_LOCAL.get().logSbeMessage(tag, encoder);
        }
    }

    public static void logSbeMessage(
        final LogTag tag,
        final DisconnectEncoder encoder)
    {
        if (isEnabled(tag))
        {
            THREAD_LOCAL.get().logSbeMessage(tag, encoder);
        }
    }

    public static void logSbeMessage(
        final LogTag tag,
        final ConnectEncoder encoder)
    {
        if (isEnabled(tag))
        {
            THREAD_LOCAL.get().logSbeMessage(tag, encoder);
        }
    }

    public static void logSbeMessage(
        final LogTag tag,
        final ResetSessionIdsEncoder encoder)
    {
        if (isEnabled(tag))
        {
            THREAD_LOCAL.get().logSbeMessage(tag, encoder);
        }
    }

    public static void logSbeMessage(
        final LogTag tag,
        final ResetSequenceNumberEncoder encoder)
    {
        if (isEnabled(tag))
        {
            THREAD_LOCAL.get().logSbeMessage(tag, encoder);
        }
    }

    public static void logSbeMessage(
        final LogTag tag,
        final ResetLibrarySequenceNumberEncoder encoder)
    {
        if (isEnabled(tag))
        {
            THREAD_LOCAL.get().logSbeMessage(tag, encoder);
        }
    }

    public static void logSbeMessage(
        final LogTag tag,
        final RequestDisconnectEncoder encoder)
    {
        if (isEnabled(tag))
        {
            THREAD_LOCAL.get().logSbeMessage(tag, encoder);
        }
    }

    public static void logSbeMessage(
        final LogTag tag,
        final MidConnectionDisconnectEncoder encoder)
    {
        if (isEnabled(tag))
        {
            THREAD_LOCAL.get().logSbeMessage(tag, encoder);
        }
    }

    public static void logSbeMessage(
        final LogTag tag,
        final InitiateConnectionEncoder encoder)
    {
        if (isEnabled(tag))
        {
            THREAD_LOCAL.get().logSbeMessage(tag, encoder);
        }
    }

    public static void logSbeMessage(
        final LogTag tag,
        final ErrorEncoder encoder)
    {
        if (isEnabled(tag))
        {
            THREAD_LOCAL.get().logSbeMessage(tag, encoder);
        }
    }

    public static void logSbeMessage(
        final LogTag tag,
        final ApplicationHeartbeatEncoder encoder)
    {
        if (isEnabled(tag))
        {
            THREAD_LOCAL.get().logSbeMessage(tag, encoder);
        }
    }

    public static void logSbeMessage(
        final LogTag tag,
        final LibraryConnectEncoder encoder)
    {
        if (isEnabled(tag))
        {
            THREAD_LOCAL.get().logSbeMessage(tag, encoder);
        }
    }

    public static void logSbeMessage(
        final LogTag tag,
        final ReleaseSessionEncoder encoder)
    {
        if (isEnabled(tag))
        {
            THREAD_LOCAL.get().logSbeMessage(tag, encoder);
        }
    }

    public static void logSbeMessage(
        final LogTag tag,
        final ReleaseSessionReplyEncoder encoder)
    {
        if (isEnabled(tag))
        {
            THREAD_LOCAL.get().logSbeMessage(tag, encoder);
        }
    }

    public static void logSbeMessage(
        final LogTag tag,
        final RequestSessionEncoder encoder)
    {
        if (isEnabled(tag))
        {
            THREAD_LOCAL.get().logSbeMessage(tag, encoder);
        }
    }

    public static void logSbeMessage(
        final LogTag tag,
        final RequestSessionReplyEncoder encoder)
    {
        if (isEnabled(tag))
        {
            THREAD_LOCAL.get().logSbeMessage(tag, encoder);
        }
    }

    public static void logSbeMessage(
        final LogTag tag,
        final LibraryTimeoutEncoder encoder)
    {
        if (isEnabled(tag))
        {
            THREAD_LOCAL.get().logSbeMessage(tag, encoder);
        }
    }

    public static void logSbeMessage(
        final LogTag tag,
        final ControlNotificationEncoder encoder)
    {
        if (isEnabled(tag))
        {
            THREAD_LOCAL.get().logSbeMessage(tag, encoder);
        }
    }

    public static void logSbeMessage(
        final LogTag tag,
        final SlowStatusNotificationEncoder encoder)
    {
        if (isEnabled(tag))
        {
            THREAD_LOCAL.get().logSbeMessage(tag, encoder);
        }
    }

    public static void logSbeMessage(
        final LogTag tag,
        final FollowerSessionRequestEncoder encoder)
    {
        if (isEnabled(tag))
        {
            THREAD_LOCAL.get().logSbeMessage(tag, encoder);
        }
    }

    public static void logSbeMessage(
        final LogTag tag,
        final FollowerSessionReplyEncoder encoder)
    {
        if (isEnabled(tag))
        {
            THREAD_LOCAL.get().logSbeMessage(tag, encoder);
        }
    }

    public static void logSbeMessage(
        final LogTag tag,
        final EndOfDayEncoder encoder)
    {
        if (isEnabled(tag))
        {
            THREAD_LOCAL.get().logSbeMessage(tag, encoder);
        }
    }

    public static void logSbeMessage(
        final LogTag tag,
        final WriteMetaDataEncoder encoder)
    {
        if (isEnabled(tag))
        {
            THREAD_LOCAL.get().logSbeMessage(tag, encoder);
        }
    }

    public static void logSbeMessage(
        final LogTag tag,
        final WriteMetaDataReplyEncoder encoder)
    {
        if (isEnabled(tag))
        {
            THREAD_LOCAL.get().logSbeMessage(tag, encoder);
        }
    }

    public static void logSbeMessage(
        final LogTag tag,
        final ReadMetaDataEncoder encoder)
    {
        if (isEnabled(tag))
        {
            THREAD_LOCAL.get().logSbeMessage(tag, encoder);
        }
    }

    public static void logSbeMessage(
        final LogTag tag,
        final ReadMetaDataReplyEncoder encoder)
    {
        if (isEnabled(tag))
        {
            THREAD_LOCAL.get().logSbeMessage(tag, encoder);
        }
    }

    public static void logSbeMessage(
        final LogTag tag,
        final ReplayMessagesEncoder encoder)
    {
        if (isEnabled(tag))
        {
            THREAD_LOCAL.get().logSbeMessage(tag, encoder);
        }
    }

    public static void logSbeMessage(
        final LogTag tag,
        final ReplayMessagesReplyEncoder encoder)
    {
        if (isEnabled(tag))
        {
            THREAD_LOCAL.get().logSbeMessage(tag, encoder);
        }
    }

    public static void logSbeMessage(
        final LogTag tag, final InitiateILinkConnectionEncoder encoder)
    {
        if (isEnabled(tag))
        {
            THREAD_LOCAL.get().logSbeMessage(tag, encoder);
        }
    }

    public static void logSbeMessage(
        final LogTag tag, final ILinkConnectEncoder encoder)
    {
        if (isEnabled(tag))
        {
            THREAD_LOCAL.get().logSbeMessage(tag, encoder);
        }
    }

    public static void logSbeMessage(
        final LogTag tag, final LibraryExtendPositionEncoder encoder)
    {
        if (isEnabled(tag))
        {
            THREAD_LOCAL.get().logSbeMessage(tag, encoder);
        }
    }

    public static void logSbeMessage(
        final LogTag tag, final ValidResendRequestEncoder encoder)
    {
        if (isEnabled(tag))
        {
            THREAD_LOCAL.get().logSbeMessage(tag, encoder);
        }
    }

    public static void logSbeDecoder(
        final LogTag tag, final String prefix, final Consumer<StringBuilder> appendTo)
    {
        if (isEnabled(tag))
        {
            THREAD_LOCAL.get().logSbeDecoder(tag, prefix, appendTo);
        }
    }

    public static void log(
        final LogTag tag,
        final String prefixString,
        final DirectBuffer buffer,
        final int offset,
        final int length)
    {
        if (isEnabled(tag))
        {
            THREAD_LOCAL.get().log(tag, prefixString, buffer, offset, length);
        }
    }

    public static void logBytes(
        final LogTag tag,
        final String prefixString,
        final ByteBuffer buffer,
        final int offset,
        final int length)
    {
        if (isEnabled(tag))
        {
            THREAD_LOCAL.get().logBytes(tag, prefixString, buffer, offset, length);
        }
    }

    public static void log(
        final LogTag tag,
        final String prefixString,
        final ByteBuffer byteBuffer,
        final int length)
    {
        if (isEnabled(tag))
        {
            THREAD_LOCAL.get().log(tag, prefixString, byteBuffer, length);
        }
    }

    public static void log(
        final LogTag tag,
        final String message)
    {
        if (isEnabled(tag))
        {
            THREAD_LOCAL.get().log(tag, message);
        }
    }

    public static void log(
        final LogTag tag,
        final String prefixString,
        final String suffixString)
    {
        if (isEnabled(tag))
        {
            THREAD_LOCAL.get().log(tag, prefixString, suffixString);
        }
    }

    public static void log(
        final LogTag tag,
        final CharFormatter formatter,
        final long first,
        final String second)
    {
        if (isEnabled(tag))
        {
            formatter.clear().with(first).with(second);
            THREAD_LOCAL.get().log(tag, formatter);
        }
    }

    public static void log(
        final LogTag tag,
        final CharFormatter formatter,
        final long first)
    {
        if (isEnabled(tag))
        {
            formatter.clear().with(first);
            THREAD_LOCAL.get().log(tag, formatter);
        }
    }

    public static void log(
        final LogTag tag,
        final CharFormatter formatter,
        final long first,
        final long second)
    {
        if (isEnabled(tag))
        {
            formatter.clear().with(first).with(second);
            THREAD_LOCAL.get().log(tag, formatter);
        }
    }

    public static void log(
        final LogTag tag,
        final CharFormatter formatter,
        final long first,
        final long second,
        final long third)
    {
        if (isEnabled(tag))
        {
            formatter.clear().with(first).with(second).with(third);
            THREAD_LOCAL.get().log(tag, formatter);
        }
    }

    public static void log(
        final LogTag tag,
        final CharFormatter formatter,
        final String first,
        final long second,
        final long third)
    {
        if (isEnabled(tag))
        {
            formatter.clear().with(first).with(second).with(third);
            THREAD_LOCAL.get().log(tag, formatter);
        }
    }

    public static void log(
        final LogTag tag,
        final CharFormatter formatter,
        final long first,
        final long second,
        final String third)
    {
        if (isEnabled(tag))
        {
            formatter.clear().with(first).with(second).with(third);
            THREAD_LOCAL.get().log(tag, formatter);
        }
    }

    public static void log(
        final LogTag tag,
        final CharFormatter formatter,
        final long first,
        final long second,
        final long third,
        final long fourth)
    {
        if (isEnabled(tag))
        {
            formatter.clear().with(first).with(second).with(third).with(fourth);
            THREAD_LOCAL.get().log(tag, formatter);
        }
    }

    public static void log(
        final LogTag tag,
        final CharFormatter formatter,
        final String first,
        final long second,
        final long third,
        final long fourth)
    {
        if (isEnabled(tag))
        {
            formatter.clear().with(first).with(second).with(third).with(fourth);
            THREAD_LOCAL.get().log(tag, formatter);
        }
    }

    // Used by fix-integration project
    public static void log(
        final LogTag tag,
        final String formatString,
        final Object first,
        final Object second)
    {
        if (isEnabled(tag))
        {
            THREAD_LOCAL.get().log(tag, String.format(formatString, first, second));
        }
    }

    static String threadName()
    {
        return Thread.currentThread().getName();
    }

    private static void substituteSeparator(final byte[] data)
    {
        if (needsSeparatorSubstitution())
        {
            final int size = data.length;
            for (int i = 0; i < size; i++)
            {
                if (data[i] == DEFAULT_DEBUG_LOGGING_SEPARATOR)
                {
                    data[i] = DEBUG_LOGGING_SEPARATOR;
                }
            }
        }
    }

    private static boolean needsSeparatorSubstitution()
    {
        return DEBUG_LOGGING_SEPARATOR != DEFAULT_DEBUG_LOGGING_SEPARATOR;
    }

    public static boolean isEnabled(final LogTag tag)
    {
        return DEBUG_PRINT_MESSAGES && DEBUG_TAGS.contains(tag);
    }

    static class ThreadLocalLogger
    {
        // Library -> Engine
        private final InitiateConnectionDecoder initiateConnection = new InitiateConnectionDecoder();
        private final RequestDisconnectDecoder requestDisconnect = new RequestDisconnectDecoder();
        private final MidConnectionDisconnectDecoder midConnectionDisconnect = new MidConnectionDisconnectDecoder();
        private final LibraryConnectDecoder libraryConnect = new LibraryConnectDecoder();
        private final ReleaseSessionDecoder releaseSession = new ReleaseSessionDecoder();
        private final RequestSessionDecoder requestSession = new RequestSessionDecoder();
        private final FollowerSessionRequestDecoder followerSessionRequest = new FollowerSessionRequestDecoder();
        private final WriteMetaDataDecoder writeMetaData = new WriteMetaDataDecoder();
        private final ReadMetaDataDecoder readMetaData = new ReadMetaDataDecoder();
        private final ReplayMessagesDecoder replayMessages = new ReplayMessagesDecoder();
        private final ConnectDecoder connect = new ConnectDecoder();
        private final ResetSessionIdsDecoder resetSessionIds = new ResetSessionIdsDecoder();
        private final LibraryTimeoutDecoder libraryTimeout = new LibraryTimeoutDecoder();
        private final InitiateILinkConnectionDecoder initiateILinkConnection =
            new InitiateILinkConnectionDecoder();

        // Engine -> Library
        private final ErrorDecoder error = new ErrorDecoder();
        private final ReleaseSessionReplyDecoder releaseSessionReply = new ReleaseSessionReplyDecoder();
        private final RequestSessionReplyDecoder requestSessionReply = new RequestSessionReplyDecoder();
        private final WriteMetaDataReplyDecoder writeMetaDataReply = new WriteMetaDataReplyDecoder();
        private final ReadMetaDataReplyDecoder readMetaDataReply = new ReadMetaDataReplyDecoder();
        private final ControlNotificationDecoder controlNotification = new ControlNotificationDecoder();
        private final SlowStatusNotificationDecoder slowStatusNotification = new SlowStatusNotificationDecoder();
        private final ResetLibrarySequenceNumberDecoder resetLibrarySequenceNumber =
            new ResetLibrarySequenceNumberDecoder();
        private final ResetSequenceNumberDecoder resetSequenceNumber =
            new ResetSequenceNumberDecoder();
        private final ManageSessionDecoder manageSession = new ManageSessionDecoder();
        private final FollowerSessionReplyDecoder followerSessionReply = new FollowerSessionReplyDecoder();
        private final EndOfDayDecoder endOfDay = new EndOfDayDecoder();
        private final ReplayMessagesReplyDecoder replayMessagesReply = new ReplayMessagesReplyDecoder();
        private final ValidResendRequestDecoder validResendRequest = new ValidResendRequestDecoder();
        private final LibraryExtendPositionDecoder libraryExtendPosition = new LibraryExtendPositionDecoder();
        private final ILinkConnectDecoder iLinkConnect = new ILinkConnectDecoder();

        // Common
        private final ApplicationHeartbeatDecoder applicationHeartbeat = new ApplicationHeartbeatDecoder();
        private final DisconnectDecoder disconnect = new DisconnectDecoder();
        private final RedactSequenceUpdateDecoder redactSequenceUpdate = new RedactSequenceUpdateDecoder();

        private final StringBuilder builder = new StringBuilder();

        private byte[] bytes = new byte[0];
        private final AsciiSequenceView asciiView = new AsciiSequenceView();
        private final UnsafeBuffer buffer = new UnsafeBuffer(bytes);
        private final ThreadLocalAppender appender;

        final boolean isThreadEnabled;

        ThreadLocalLogger()
        {
            final String threadName = threadName();
            isThreadEnabled = DEBUG_PRINT_THREAD == null || DEBUG_PRINT_THREAD.equals(threadName);
            appender = APPENDER.makeLocalAppender();
        }

        public void logSbeMessage(final LogTag tag, final RedactSequenceUpdateEncoder encoder)
        {
            appendStart();
            redactSequenceUpdate.wrap(
                encoder.buffer(),
                encoder.initialOffset(),
                RedactSequenceUpdateEncoder.BLOCK_LENGTH,
                RedactSequenceUpdateEncoder.SCHEMA_VERSION);
            redactSequenceUpdate.appendTo(builder);
            finish(tag);
        }

        public void logSbeMessage(final LogTag tag, final ManageSessionEncoder encoder)
        {
            appendStart();
            manageSession.wrap(
                encoder.buffer(),
                encoder.initialOffset(),
                ManageSessionEncoder.BLOCK_LENGTH,
                ManageSessionEncoder.SCHEMA_VERSION);
            manageSession.appendTo(builder);
            finish(tag);
        }

        public void logSbeMessage(
            final LogTag tag,
            final DisconnectEncoder encoder)
        {
            appendStart();
            disconnect.wrap(
                encoder.buffer(),
                encoder.initialOffset(),
                DisconnectEncoder.BLOCK_LENGTH,
                DisconnectEncoder.SCHEMA_VERSION);
            disconnect.appendTo(builder);
            finish(tag);
        }

        public void logSbeMessage(
            final LogTag tag,
            final ConnectEncoder encoder)
        {
            appendStart();
            connect.wrap(
                encoder.buffer(),
                encoder.initialOffset(),
                ConnectEncoder.BLOCK_LENGTH,
                ConnectEncoder.SCHEMA_VERSION);
            connect.appendTo(builder);
            finish(tag);
        }

        public void logSbeMessage(
            final LogTag tag,
            final ResetSessionIdsEncoder encoder)
        {
            appendStart();
            resetSessionIds.wrap(
                encoder.buffer(),
                encoder.initialOffset(),
                ResetSessionIdsEncoder.BLOCK_LENGTH,
                ResetSessionIdsEncoder.SCHEMA_VERSION);
            resetSessionIds.appendTo(builder);
            finish(tag);
        }

        public void logSbeMessage(
            final LogTag tag,
            final ResetSequenceNumberEncoder encoder)
        {
            appendStart();
            resetSequenceNumber.wrap(
                encoder.buffer(),
                encoder.initialOffset(),
                ResetSequenceNumberEncoder.BLOCK_LENGTH,
                ResetSequenceNumberEncoder.SCHEMA_VERSION);
            resetSequenceNumber.appendTo(builder);
            finish(tag);
        }

        public void logSbeMessage(
            final LogTag tag,
            final ResetLibrarySequenceNumberEncoder encoder)
        {
            appendStart();
            resetLibrarySequenceNumber.wrap(
                encoder.buffer(),
                encoder.initialOffset(),
                ResetLibrarySequenceNumberEncoder.BLOCK_LENGTH,
                ResetLibrarySequenceNumberEncoder.SCHEMA_VERSION);
            resetLibrarySequenceNumber.appendTo(builder);
            finish(tag);
        }

        public void logSbeMessage(
            final LogTag tag,
            final RequestDisconnectEncoder encoder)
        {
            appendStart();
            requestDisconnect.wrap(
                encoder.buffer(),
                encoder.initialOffset(),
                RequestDisconnectEncoder.BLOCK_LENGTH,
                RequestDisconnectEncoder.SCHEMA_VERSION);
            requestDisconnect.appendTo(builder);
            finish(tag);
        }

        public void logSbeMessage(
            final LogTag tag,
            final MidConnectionDisconnectEncoder encoder)
        {
            appendStart();
            midConnectionDisconnect.wrap(
                encoder.buffer(),
                encoder.initialOffset(),
                MidConnectionDisconnectEncoder.BLOCK_LENGTH,
                MidConnectionDisconnectEncoder.SCHEMA_VERSION);
            midConnectionDisconnect.appendTo(builder);
            finish(tag);
        }

        public void logSbeMessage(
            final LogTag tag,
            final InitiateConnectionEncoder encoder)
        {
            appendStart();
            initiateConnection.wrap(
                encoder.buffer(),
                encoder.initialOffset(),
                InitiateConnectionEncoder.BLOCK_LENGTH,
                InitiateConnectionEncoder.SCHEMA_VERSION);
            initiateConnection.appendTo(builder);
            finish(tag);
        }

        public void logSbeMessage(
            final LogTag tag,
            final ErrorEncoder encoder)
        {
            appendStart();
            error.wrap(
                encoder.buffer(),
                encoder.initialOffset(),
                ErrorEncoder.BLOCK_LENGTH,
                ErrorEncoder.SCHEMA_VERSION);
            error.appendTo(builder);
            finish(tag);
        }

        public void logSbeMessage(
            final LogTag tag,
            final ApplicationHeartbeatEncoder encoder)
        {
            appendStart();
            applicationHeartbeat.wrap(
                encoder.buffer(),
                encoder.initialOffset(),
                ApplicationHeartbeatEncoder.BLOCK_LENGTH,
                ApplicationHeartbeatEncoder.SCHEMA_VERSION);
            applicationHeartbeat.appendTo(builder);
            finish(tag);
        }

        public void logSbeMessage(
            final LogTag tag,
            final LibraryConnectEncoder encoder)
        {
            appendStart();
            libraryConnect.wrap(
                encoder.buffer(),
                encoder.initialOffset(),
                LibraryConnectEncoder.BLOCK_LENGTH,
                LibraryConnectEncoder.SCHEMA_VERSION);
            libraryConnect.appendTo(builder);
            finish(tag);
        }

        public void logSbeMessage(
            final LogTag tag,
            final ReleaseSessionEncoder encoder)
        {
            appendStart();
            releaseSession.wrap(
                encoder.buffer(),
                encoder.initialOffset(),
                ReleaseSessionEncoder.BLOCK_LENGTH,
                ReleaseSessionEncoder.SCHEMA_VERSION);
            releaseSession.appendTo(builder);
            finish(tag);
        }

        public void logSbeMessage(
            final LogTag tag,
            final ReleaseSessionReplyEncoder encoder)
        {
            appendStart();
            releaseSessionReply.wrap(
                encoder.buffer(),
                encoder.initialOffset(),
                ReleaseSessionReplyEncoder.BLOCK_LENGTH,
                ReleaseSessionReplyEncoder.SCHEMA_VERSION);
            releaseSessionReply.appendTo(builder);
            finish(tag);
        }

        public void logSbeMessage(
            final LogTag tag,
            final RequestSessionEncoder encoder)
        {
            appendStart();
            requestSession.wrap(
                encoder.buffer(),
                encoder.initialOffset(),
                RequestSessionEncoder.BLOCK_LENGTH,
                RequestSessionEncoder.SCHEMA_VERSION);
            requestSession.appendTo(builder);
            finish(tag);
        }

        public void logSbeMessage(
            final LogTag tag,
            final RequestSessionReplyEncoder encoder)
        {
            appendStart();
            requestSessionReply.wrap(
                encoder.buffer(),
                encoder.initialOffset(),
                RequestSessionReplyEncoder.BLOCK_LENGTH,
                RequestSessionReplyEncoder.SCHEMA_VERSION);
            requestSessionReply.appendTo(builder);
            finish(tag);
        }

        public void logSbeMessage(
            final LogTag tag,
            final LibraryTimeoutEncoder encoder)
        {
            appendStart();
            libraryTimeout.wrap(
                encoder.buffer(),
                encoder.initialOffset(),
                LibraryTimeoutEncoder.BLOCK_LENGTH,
                LibraryTimeoutEncoder.SCHEMA_VERSION);
            libraryTimeout.appendTo(builder);
            finish(tag);
        }

        public void logSbeMessage(
            final LogTag tag,
            final ControlNotificationEncoder encoder)
        {
            appendStart();
            controlNotification.wrap(
                encoder.buffer(),
                encoder.initialOffset(),
                ControlNotificationEncoder.BLOCK_LENGTH,
                ControlNotificationEncoder.SCHEMA_VERSION);
            controlNotification.appendTo(builder);
            finish(tag);
        }

        public void logSbeMessage(
            final LogTag tag,
            final SlowStatusNotificationEncoder encoder)
        {
            appendStart();
            slowStatusNotification.wrap(
                encoder.buffer(),
                encoder.initialOffset(),
                SlowStatusNotificationEncoder.BLOCK_LENGTH,
                SlowStatusNotificationEncoder.SCHEMA_VERSION);
            slowStatusNotification.appendTo(builder);
            finish(tag);
        }

        public void logSbeMessage(
            final LogTag tag,
            final FollowerSessionRequestEncoder encoder)
        {
            appendStart();
            followerSessionRequest.wrap(
                encoder.buffer(),
                encoder.initialOffset(),
                FollowerSessionRequestEncoder.BLOCK_LENGTH,
                FollowerSessionRequestEncoder.SCHEMA_VERSION);
            followerSessionRequest.appendTo(builder);
            finish(tag);
        }

        public void logSbeMessage(
            final LogTag tag,
            final FollowerSessionReplyEncoder encoder)
        {
            appendStart();
            followerSessionReply.wrap(
                encoder.buffer(),
                encoder.initialOffset(),
                FollowerSessionReplyEncoder.BLOCK_LENGTH,
                FollowerSessionReplyEncoder.SCHEMA_VERSION);
            followerSessionReply.appendTo(builder);
            finish(tag);
        }

        public void logSbeMessage(
            final LogTag tag,
            final EndOfDayEncoder encoder)
        {
            appendStart();
            endOfDay.wrap(
                encoder.buffer(),
                encoder.initialOffset(),
                EndOfDayEncoder.BLOCK_LENGTH,
                EndOfDayEncoder.SCHEMA_VERSION);
            endOfDay.appendTo(builder);
            finish(tag);
        }

        public void logSbeMessage(
            final LogTag tag,
            final WriteMetaDataEncoder encoder)
        {
            appendStart();
            writeMetaData.wrap(
                encoder.buffer(),
                encoder.initialOffset(),
                WriteMetaDataEncoder.BLOCK_LENGTH,
                WriteMetaDataEncoder.SCHEMA_VERSION);
            writeMetaData.appendTo(builder);
            finish(tag);
        }

        public void logSbeMessage(
            final LogTag tag,
            final WriteMetaDataReplyEncoder encoder)
        {
            appendStart();
            writeMetaDataReply.wrap(
                encoder.buffer(),
                encoder.initialOffset(),
                WriteMetaDataReplyEncoder.BLOCK_LENGTH,
                WriteMetaDataReplyEncoder.SCHEMA_VERSION);
            writeMetaDataReply.appendTo(builder);
            finish(tag);
        }

        public void logSbeMessage(
            final LogTag tag,
            final ReadMetaDataEncoder encoder)
        {
            appendStart();
            readMetaData.wrap(
                encoder.buffer(),
                encoder.initialOffset(),
                ReadMetaDataEncoder.BLOCK_LENGTH,
                ReadMetaDataEncoder.SCHEMA_VERSION);
            readMetaData.appendTo(builder);
            finish(tag);
        }

        public void logSbeMessage(
            final LogTag tag,
            final ReadMetaDataReplyEncoder encoder)
        {
            appendStart();
            readMetaDataReply.wrap(
                encoder.buffer(),
                encoder.initialOffset(),
                ReadMetaDataReplyEncoder.BLOCK_LENGTH,
                ReadMetaDataReplyEncoder.SCHEMA_VERSION);
            readMetaDataReply.appendTo(builder);
            finish(tag);
        }

        public void logSbeMessage(
            final LogTag tag,
            final ReplayMessagesEncoder encoder)
        {
            appendStart();
            replayMessages.wrap(
                encoder.buffer(),
                encoder.initialOffset(),
                ReplayMessagesEncoder.BLOCK_LENGTH,
                ReplayMessagesEncoder.SCHEMA_VERSION);
            replayMessages.appendTo(builder);
            finish(tag);
        }

        public void logSbeMessage(
            final LogTag tag,
            final ReplayMessagesReplyEncoder encoder)
        {
            appendStart();
            replayMessagesReply.wrap(
                encoder.buffer(),
                encoder.initialOffset(),
                ReplayMessagesReplyEncoder.BLOCK_LENGTH,
                ReplayMessagesReplyEncoder.SCHEMA_VERSION);
            replayMessagesReply.appendTo(builder);
            finish(tag);
        }

        public void logSbeMessage(
            final LogTag tag,
            final ValidResendRequestEncoder encoder)
        {
            appendStart();
            validResendRequest.wrap(
                encoder.buffer(),
                encoder.initialOffset(),
                ValidResendRequestEncoder.BLOCK_LENGTH,
                ValidResendRequestEncoder.SCHEMA_VERSION);
            validResendRequest.appendTo(builder);
            finish(tag);
        }

        public void logSbeMessage(
            final LogTag tag,
            final LibraryExtendPositionEncoder encoder)
        {
            appendStart();
            libraryExtendPosition.wrap(
                encoder.buffer(),
                encoder.initialOffset(),
                LibraryExtendPositionEncoder.BLOCK_LENGTH,
                LibraryExtendPositionEncoder.SCHEMA_VERSION);
            libraryExtendPosition.appendTo(builder);
            finish(tag);
        }

        public void logSbeMessage(final LogTag tag, final InitiateILinkConnectionEncoder encoder)
        {
            appendStart();
            initiateILinkConnection.wrap(
                encoder.buffer(),
                encoder.initialOffset(),
                InitiateILinkConnectionEncoder.BLOCK_LENGTH,
                InitiateILinkConnectionEncoder.SCHEMA_VERSION);
            initiateILinkConnection.appendTo(builder);
            finish(tag);
        }

        public void logSbeMessage(final LogTag tag, final ILinkConnectEncoder encoder)
        {
            appendStart();
            iLinkConnect.wrap(
                encoder.buffer(),
                encoder.initialOffset(),
                ILinkConnectEncoder.BLOCK_LENGTH,
                ILinkConnectEncoder.SCHEMA_VERSION);
            iLinkConnect.appendTo(builder);
            finish(tag);
        }

        public void logSbeDecoder(final LogTag tag, final String prefix, final Consumer<StringBuilder> appendTo)
        {
            appendStart();
            builder.append(prefix);
            appendTo.accept(builder);
            finish(tag);
        }

        private void appendStart()
        {
            final StringBuilder builder = this.builder;
            builder.setLength(0);
        }

        public void log(final LogTag tag, final String prefixString, final ByteBuffer byteBuffer, final int length)
        {
            final byte[] data = getByteArray(length);
            byteBuffer.get(data, 0, length);
            substituteSeparator(data);

            appendStart();
            final StringBuilder builder = this.builder;
            builder.append(prefixString);
            final AsciiSequenceView asciiView = this.asciiView;
            asciiView.wrap(buffer, 0, length);
            builder.append(asciiView);
            finish(tag);
        }

        public void logBytes(
            final LogTag tag,
            final String prefixString,
            final ByteBuffer byteBuffer,
            final int offset,
            final int length)
        {
            appendStart();
            final StringBuilder builder = this.builder;
            builder.append(prefixString);

            if (length == 0)
            {
                builder.append("{}");
            }
            else
            {
                builder.append('{');
                for (int i = 0; i < length; i++)
                {
                    builder.append(byteBuffer.get(offset + i));
                    if (i == length - 1)
                    {
                        builder.append('}');
                    }
                    else
                    {
                        builder.append(", ");
                    }
                }
            }
            finish(tag);
        }

        private byte[] getByteArray(final int length)
        {
            byte[] data = this.bytes;
            if (data.length < length)
            {
                this.bytes = data = new byte[length];
                buffer.wrap(data);
            }
            return data;
        }

        public void log(
            final LogTag tag, final String message)
        {
            appendStart();
            builder.append(message);
            finish(tag);
        }

        public void log(
            final LogTag tag, final String prefixString, final DirectBuffer buffer, final int offset, final int length)
        {
            appendStart();
            builder.append(prefixString);

            if (needsSeparatorSubstitution())
            {
                final byte[] data = getByteArray(length);
                buffer.getBytes(offset, data, 0, length);
                substituteSeparator(data);
                asciiView.wrap(this.buffer, 0, length);
            }
            else
            {
                asciiView.wrap(buffer, offset, length);
            }

            builder.append(asciiView);
            finish(tag);
        }

        public void log(
            final LogTag tag,
            final CharFormatter formatter,
            final DirectBuffer buffer,
            final int bufferOffset,
            final int bufferLength)
        {
            final byte[] data = getByteArray(bufferLength);
            buffer.getBytes(bufferOffset, data, 0, bufferLength);
            substituteSeparator(data);

            formatter.with(data, bufferLength);

            log(tag, formatter);
        }

        public void log(
            final LogTag tag,
            final String prefixString,
            final String suffixString)
        {
            appendStart();
            final StringBuilder builder = this.builder;
            builder.append(prefixString);
            builder.append(suffixString);
            finish(tag);
        }

        public void log(
            final LogTag tag,
            final CharFormatter formatter)
        {
            appendStart();
            formatter.appendTo(builder);
            finish(tag);
        }

        private void finish(final LogTag tag)
        {
            final StringBuilder builder = this.builder;
            builder.append(System.lineSeparator());
            appender.log(tag, builder);
        }
    }
}