All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.almworks.jira.structure.util.SyncLogger Maven / Gradle / Ivy

There is a newer version: 17.25.3
Show newest version
package com.almworks.jira.structure.util;

import com.almworks.integers.LongIterable;
import com.almworks.integers.LongIterator;
import com.almworks.jira.structure.api.StructureAuth;
import com.almworks.jira.structure.api.StructureException;
import com.almworks.jira.structure.api2g.forest.Forest;
import com.almworks.jira.structure.api2g.forest.ItemForest;
import com.almworks.jira.structure.api2g.item.CoreIdentities;
import com.almworks.jira.structure.api2g.item.ItemIdentity;
import com.almworks.jira.structure.api2g.row.RowManager;
import com.almworks.jira.structure.api2g.structure.StructureManager;
import com.almworks.jira.structure.api2g.sync.StructureSynchronizer;
import com.almworks.jira.structure.api2g.sync.SyncInstance;
import com.almworks.jira.structure.api2g.v2.MissingRowException;
import com.atlassian.annotations.Internal;
import com.atlassian.jira.issue.Issue;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;

import javax.annotation.concurrent.NotThreadSafe;
import java.util.*;

import static com.google.common.collect.Iterators.*;

/** 
 * This is a utility class to log messages from {@link com.almworks.jira.structure.api2g.sync.StructureSynchronizer synchronizer implementations}.
 * It prepends the specified messages with the following information:
 * 
    *
  • name of the synchronizer taken from its configuration in the set up in the <structure-synchronizer> module (see {@link com.almworks.jira.structure.api2g.sync.StructureSynchronizerModuleDescriptor#getLabel()},
  • *
  • mode of synchronization (one-time synchronization, periodical autosync, or resync),
  • *
  • name and ID of the structure being synchronized,
  • *
  • ID of the {@link SyncInstance synchronizer instance}.
  • *
* *

Name of the user under which the synchronization is run is not provided, as it is automatically inserted into the log message by JIRA.

* *

Features

*

There is a bunch of helper methods to pretty-print the user under which the synchronizer runs ({@link #username()}), * produce warnings about typical {@link StructureException StructureExceptions} that may be encountered by synchronizers ({@link #warnStructureException(com.almworks.jira.structure.api.StructureException)}), * and select messages based on the synchronization mode ({@link #selectBySyncMode(Object, Object, Object)}). *

* *

All logging methods that take {@code Object...} vararg parameter delimit its contents putting a single space before the parameter. If the parameter is a character or a single-character String * that is a punctuation mark, the space is not inserted before the parameter. If the parameter is an {@code Object[]}, its contents are added to the output as if they belonged to the original {@code Object...} parameter. *

* *

Example:

*

{@code SyncLogger log = ... ; log.warn("cannot run because of", isKaboozle() ? "kaboozle" : new Object[]{"grumbles with error", getError()}, ':', getCause())}

*

will produce the following log message:

*

{@code autosync #239 for structure 'Unresolved in 2.0' (#156) cannot run because of grumbles with error FATAL: something went wrong}

* *

Methods that end with {@code exceptionIfDebug} attempt to log exception stack trace if log level is DEBUG or lower and print only exception message if log level is higher.

* * @see StructureSynchronizer * @since 7.2.0 (Structure 2.0) */ @NotThreadSafe public class SyncLogger { private static final ThreadLocal SYNC_LOGGER_THREAD_LOCAL = new ThreadLocal<>(); @Nullable private final SyncInstance mySync; private final StructureManager myStructureManager; private final RowManager myRowManager; private final boolean myAuto; private Logger myLog; @NotNull private String myPrefix = ""; private final Deque myPrefixStack = new ArrayDeque<>(); public SyncLogger(Logger log, @Nullable SyncInstance sync, StructureManager structureManager, RowManager rowManager, boolean auto) { myLog = log; mySync = sync; myStructureManager = structureManager; myRowManager = rowManager; myAuto = auto; } /** *

Retrieves SyncLogger from the thread-local storage. Must only be called during synchronization; if called * at any other time, throws an {@link UnsupportedOperationException}. * *

The returned SyncLogger delegates to {@link Logger} for the current {@link StructureSynchronizer} class.

* */ @NotNull public static SyncLogger get() { SyncLogger slog = SYNC_LOGGER_THREAD_LOCAL.get(); if (slog == null) throw new IllegalStateException("SyncLogger not installed - maybe not in synchronization thread?"); return slog; } /** * This is internal method that is used by the synchronization subsystem. Do not use this method. Call from threads * other than the synchronization thread will result in {@link UnsupportedOperationException} being thrown. * */ @Internal public static void set(@Nullable SyncLogger syncLog) { ClassLoader threadClassLoader = Thread.currentThread().getContextClassLoader(); if (!SyncLogger.class.getClassLoader().equals(threadClassLoader)) { throw new UnsupportedOperationException(); } SYNC_LOGGER_THREAD_LOCAL.set(syncLog); } public static boolean isInfo() { return get().getLogger().isInfoEnabled(); } public static boolean isDebug() { return get().getLogger().isDebugEnabled(); } @NotNull public String getPrefix() { return myPrefix; } public void setPrefix(@NotNull String prefix) { myPrefix = prefix == null ? "" : prefix; } public void pushPrefix(@NotNull String prefixToAppend) { myPrefixStack.push(myPrefix.length()); setPrefix(myPrefix + " " + prefixToAppend); } public void popPrefix() { myPrefix = myPrefix.substring(0, myPrefixStack.pop()); } public Logger getLogger() { return myLog; } public void setLogger(Logger logger) { myLog = logger; } public boolean isOneTimeSync() { return mySync != null && mySync.getInstanceId() == 0L; } public boolean isAutoSync() { return myAuto; } public void info(Object... msgs) { if (myLog.isInfoEnabled()) myLog.info(createLogMessage(msgs)); } public void infoException(Throwable e, Object... msgs) { if (myLog.isInfoEnabled()) myLog.info(createLogMessage(msgs), e); } public void debug(Object... msgs) { if (myLog.isDebugEnabled()) myLog.debug(createLogMessage(msgs)); } public void debugException(@Nullable Throwable ex, Object... msgs) { if (myLog.isDebugEnabled()) { if (ex != null) myLog.debug(createLogMessage(msgs), ex); else myLog.debug(createLogMessage(msgs)); } } public void warn(Object... msgs) { if (myLog.isWarnEnabled()) myLog.warn(createLogMessage(msgs)); } public void warnException(@Nullable Throwable ex, Object... msgs) { if (myLog.isWarnEnabled()) { if (ex != null) myLog.warn(createLogMessage(msgs), ex); else myLog.warn(createLogMessage(msgs)); } } public void warnExceptionIfDebug(@Nullable Throwable ex, Object... msgs) { if (myLog.isWarnEnabled()) { if (myLog.isDebugEnabled()) { warnException(ex, msgs); } else { if (ex != null) warn(msgs, ex.getMessage()); else warn(msgs); } } } public void error(Object... msgs) { if (myLog.isErrorEnabled()) myLog.error(createLogMessage(msgs)); } public void errorException(@Nullable Throwable e, Object... msgs) { if (myLog.isErrorEnabled()) { if (e != null) myLog.error(createLogMessage(msgs), e); else myLog.error(createLogMessage(msgs)); } } @NotNull public String createLogMessage(Object... msgs) { StringBuilder msgBuilder = new StringBuilder(myPrefix); appendLogMessages(msgBuilder, msgs); return msgBuilder.toString(); } private static void appendLogMessages(StringBuilder sb, Object... msgs) { if (msgs != null) { for (Object msg : msgs) { if (msg instanceof Object[]) appendLogMessages(sb, (Object[])msg); else { if (sb.length() > 0 && !isNoSpaceBefore(msg)) sb.append(' '); sb.append(msg); } } } } private static boolean isNoSpaceBefore(Object msg) { char c = msg instanceof Character ? ((Character) msg).charValue() : msg instanceof String && !((String) msg).isEmpty() ? ((String) msg).charAt(0) : 0; int type = Character.getType(c); return c != 0 && (type == Character.OTHER_PUNCTUATION || type == Character.CONTROL); } public final String defaultPrefix() { if (mySync == null) return ""; StringBuilder res = new StringBuilder(); StructureSynchronizer synchronizer = mySync.getSynchronizer(); String syncLabel; if (synchronizer != null) { syncLabel = synchronizer.getDescriptor().getLabel(); if (syncLabel == null || syncLabel.isEmpty()) syncLabel = synchronizer.getClass().getName(); } else { syncLabel = " " + mySync.getSynchronizerModuleKey(); } long syncId = mySync.getInstanceId(); boolean oneTime = isOneTimeSync(); res.append(syncLabel) .append( myAuto ? " autosync" : oneTime ? " one-time sync" : " full sync"); if (!oneTime) res.append(" #").append(syncId); res.append(" for structure "); return StructureUtil.appendDebugStructureString(mySync.getStructureId(), myStructureManager, res).toString(); } @NotNull public String username() { //j6 ok String userKey = mySync != null ? mySync.getUserKey() : StructureAuth.getUserKey(); String userName = StructureUtil.getUserNameByKey(userKey); return userName == null ? "(null)" : userName; //j6 ok } @NotNull public String issue(Long issueId) { if (myLog.isDebugEnabled()) { // In debug, forests and other info concerning issue IDs may be printed out, so having issue ID is beneficial return StructureUtil.getDebugIssueString(issueId); } // Otherwise, ID won't help much, having only keys will clutter logs less if (issueId == null) return "null"; String s = StructureUtil.getDebugIssueKey(issueId); return s != null ? s : String.valueOf(issueId); } public StringBuilder appendIssue(Long issueId, StringBuilder sb) { if (myLog.isDebugEnabled()) { // In debug, forests and other info concerning issue IDs may be printed out, so having issue ID is beneficial return StructureUtil.appendDebugIssueString(issueId, StructureUtil.getDebugIssueKey(issueId), sb); } // Otherwise, ID won't help much, having only keys will clutter logs less if (issueId == null) return sb.append("null"); String s = StructureUtil.getDebugIssueKey(issueId); return sb.append(s != null ? s : String.valueOf(issueId)); } @NotNull public String issue(@Nullable Issue issue) { if (myLog.isDebugEnabled()) { // In debug, forests and other info concerning issue IDs may be printed out, so having issue ID is beneficial return StructureUtil.getDebugIssueString(issue); } // Otherwise, ID won't help much, having only keys will clutter logs less if (issue == null) return "null"; String s = issue.getKey(); return s != null ? s : "#" + issue.getId(); } @NotNull public String issues(@Nullable Iterable issues) { if (issues == null) { return "null"; } StringBuilder sb = new StringBuilder("["); Iterator sep = concat(singletonIterator(""), cycle(", ")); for (Issue issue : issues) { sb.append(sep.next()).append(issue(issue)); } return sb.append("]").toString(); } public String issues(@Nullable LongIterable issues) { if (issues == null) return "null"; StringBuilder sb = new StringBuilder("["); Iterator sep = concat(singletonIterator(""), cycle(", ")); for (LongIterator issueIt : issues) { appendIssue(issueIt.value(), sb.append(sep.next())); } return sb.append("]").toString(); } @NotNull public String structure(final long structureId) { return StructureUtil.getDebugStructureString(structureId, myStructureManager); } public Object selectBySyncMode(Object ifAuto, Object ifResync, Object ifOneTime) { return myAuto ? ifAuto : isOneTimeSync() ? ifOneTime : ifResync; } public StringBuilder appendForest(Forest forest, StringBuilder sb) { for (int i = 0, size = forest.size(); i < size; ++i) { long rowId = forest.getRow(i); if (i > 0) sb.append(','); sb.append(rowId).append(':').append(forest.getDepth(i)).append(':'); appendItem(rowId, sb); } return sb; } public String forest(Forest forest) { return appendForest(forest, new StringBuilder()).toString(); } public StringBuilder appendItemForest(ItemForest itemForest, StringBuilder sb) { Forest forest = itemForest.getForest(); for (int i = 0, size = forest.size(); i < size; ++i) { long rowId = forest.getRow(i); if (i > 0) sb.append(','); sb.append(rowId).append(':').append(forest.getDepth(i)).append(':'); try { appendItem(itemForest.getRow(rowId).getItemId(), sb); } catch (MissingRowException e) { sb.append("'); } } return sb; } public String itemForest(ItemForest itemForest) { return appendItemForest(itemForest, new StringBuilder()).toString(); } public StringBuilder appendRows(LongIterable rowIds, StringBuilder sb) { boolean appendComma = false; for (LongIterator rowIdIt : rowIds) { long rowId = rowIdIt.value(); if (appendComma) { sb.append(','); } else { appendComma = true; } sb.append(rowId).append(':'); appendItem(rowId, sb); } return sb; } public StringBuilder appendItem(long rowId, StringBuilder sb) { try { return appendItem(myRowManager.getRow(rowId).getItemId(), sb); } catch (MissingRowException e) { return sb.append("'); } } public StringBuilder appendItem(ItemIdentity itemId, StringBuilder sb) { if (CoreIdentities.isIssue(itemId)) { return appendIssue(itemId.getLongId(), sb); } sb.append(itemId.getItemType()).append("/"); if (itemId.isLongId()) sb.append(itemId.getLongId()); else sb.append(itemId.getStringId()); return sb; } public String row(long rowId) { if (rowId == 0) return "0"; else if (rowId < 0) return "r" + rowId; try { ItemIdentity itemId = myRowManager.getRow(rowId).getItemId(); StringBuilder sb = new StringBuilder(); appendItem(itemId, sb); sb.append(" (row ").append(rowId).append(')'); return sb.toString(); } catch (MissingRowException e) { return "'; } } public String rows(LongIterable rowIds) { return appendRows(rowIds, new StringBuilder()).toString(); } /** * Contains standard error descriptions for common StructureExceptions * @return a specific string if the exception represents a common known case, null otherwise (generic message should be used) * */ @Nullable public String warnStructureException(StructureException e) { switch (e.getError()) { case STRUCTURE_NOT_EXISTS_OR_NOT_ACCESSIBLE: return warnAndReturn("cannot run because the structure does not exist or is not accessible for user", username()); case FOREST_CHANGE_PROHIBITED_BY_PARENT_PERMISSIONS: return warnAndReturn("Error while synchronizing issue", row(e.getIssue()), ":", e.getProblemDetails()); case STRUCTURE_EDIT_DENIED: return warnAndReturn("cannot run under user", username(), "because he or she does not have permissions to edit the structure"); case STRUCTURE_PLUGIN_ACCESS_DENIED: return warnAndReturn("cannot run under", username(), ':', "Structure plugin is not enabled for this user"); default: warnExceptionIfDebug(e, "encountered a problem"); return null; } } private String warnAndReturn(Object... msgs) { String message = createLogMessage(msgs); warn(message); return message; } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy