1
0
mirror of https://github.com/suk-ws/messiva.git synced 2024-11-22 11:44:50 +08:00

add new Appender and Message API

- deprecated old Message, add new IMessage API.
- deprecated old Logger.appends, uses new AppenderProvider API to provide Appenders to Logger.
- add a context on Log.
- deprecated field read on Log, use method to read those.
- add name on Logger.
- add codepoint output for SimpleFormatter.
This commit is contained in:
A.C.Sukazyo Eyre 2024-07-27 11:05:23 +08:00
parent 09c215b7ab
commit d65ea6e7bd
Signed by: Eyre_S
GPG Key ID: C17CE40291207874
17 changed files with 343 additions and 98 deletions

View File

@ -36,7 +36,7 @@ dependencies {
Set up your own logger: Set up your own logger:
```java ```java
import cc.sukazyo.messiva.logger.Logger; import cc.sukazyo.messiva.logger.Logger;
import cc.sukazyo.messiva.appender.ConsoleAppender; import cc.sukazyo.messiva.appender.impl.ConsoleAppender;
import cc.sukazyo.messiva.formatter.SimpleFormatter; import cc.sukazyo.messiva.formatter.SimpleFormatter;
// currently only implementation within messiva // currently only implementation within messiva

View File

@ -6,6 +6,14 @@ plugins {
repositories { repositories {
mavenCentral() mavenCentral()
maven {
name "wsReleases"
url "https://mvn.sukazyo.cc/releases"
}
maven {
name "wsSnapshots"
url "https://mvn.sukazyo.cc/snapshots"
}
} }
group 'cc.sukazyo' group 'cc.sukazyo'
@ -16,6 +24,7 @@ project.ext.artifactId = 'messiva'
dependencies { dependencies {
compileOnlyApi "com.github.spotbugs:spotbugs-annotations:${libSpotbugsVersion}" compileOnlyApi "com.github.spotbugs:spotbugs-annotations:${libSpotbugsVersion}"
implementation "cc.sukazyo:da4a:0.2.0-SNAPSHOT"
// implementation "org.jline:jline:${libJLineVersion}" // implementation "org.jline:jline:${libJLineVersion}"
// implementation "org.fusesource.jansi:jansi:${libJansiVersion}" // implementation "org.fusesource.jansi:jansi:${libJansiVersion}"
@ -52,6 +61,10 @@ tasks.test {
useJUnitPlatform() useJUnitPlatform()
} }
configurations.configureEach {
resolutionStrategy.cacheChangingModulesFor 1, 'seconds'
}
publishing { publishing {
repositories{ repositories{
maven { maven {

View File

@ -1,6 +1,6 @@
## Messiva ## Messiva
PROJECT_VERSION = 0.2.0 PROJECT_VERSION = 0.3.0
## dependencies ## dependencies

View File

@ -0,0 +1,13 @@
package cc.sukazyo.messiva.appender;
import cc.sukazyo.std.contexts.GivenContext;
import javax.annotation.Nonnull;
import java.util.List;
public interface AppenderProvider {
@Nonnull
List<IAppender> findAppends (@Nonnull GivenContext context);
}

View File

@ -34,7 +34,7 @@ public abstract class AppenderRestrictableByLevel extends Appender {
} }
public void pushLog (@Nonnull Log log) { public void pushLog (@Nonnull Log log) {
if (!levelSetting.checkLevel(log.level)) return; if (!levelSetting.checkLevel(log.level())) return;
pushLogChecked(log); pushLogChecked(log);
} }

View File

@ -1,33 +1,17 @@
package cc.sukazyo.messiva.appender; package cc.sukazyo.messiva.appender;
import cc.sukazyo.messiva.formatter.ILogFormatter; import cc.sukazyo.messiva.formatter.ILogFormatter;
import cc.sukazyo.messiva.log.Log;
import javax.annotation.Nonnull;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import java.util.Objects; /**
import java.util.concurrent.locks.ReentrantLock; * @deprecated moved. use {@link cc.sukazyo.messiva.appender.impl.ConsoleAppender} instead.
*/
public class ConsoleAppender extends AppenderRestrictableByLevel { @Deprecated
public class ConsoleAppender extends cc.sukazyo.messiva.appender.impl.ConsoleAppender {
@Nonnull private final ReentrantLock syncLock;
public ConsoleAppender (@Nullable ILogFormatter formatter) { public ConsoleAppender (@Nullable ILogFormatter formatter) {
super(formatter); super(formatter);
syncLock = new ReentrantLock();
}
public ConsoleAppender () {
this(null);
}
@Override
public void pushLogChecked (@Nonnull Log log) {
if (formatter == null) return;
syncLock.lock();
System.out.println(formatter.format(log));
syncLock.unlock();
} }
} }

View File

@ -8,4 +8,9 @@ public interface IAppender {
void pushLog (@Nonnull Log log); void pushLog (@Nonnull Log log);
@Nonnull
default AppenderProvider asProvider () {
return new StaticAppenderProvider(this);
}
} }

View File

@ -0,0 +1,23 @@
package cc.sukazyo.messiva.appender;
import cc.sukazyo.std.contexts.GivenContext;
import javax.annotation.Nonnull;
import java.util.Collections;
import java.util.List;
public class StaticAppenderProvider implements AppenderProvider {
@Nonnull final IAppender appender;
public StaticAppenderProvider (@Nonnull IAppender appender) {
this.appender = appender;
}
@Nonnull
@Override
public List<IAppender> findAppends (@Nonnull GivenContext context) {
return Collections.singletonList(appender);
}
}

View File

@ -0,0 +1,32 @@
package cc.sukazyo.messiva.appender.impl;
import cc.sukazyo.messiva.appender.AppenderRestrictableByLevel;
import cc.sukazyo.messiva.formatter.ILogFormatter;
import cc.sukazyo.messiva.log.Log;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.util.concurrent.locks.ReentrantLock;
public class ConsoleAppender extends AppenderRestrictableByLevel {
@Nonnull private final ReentrantLock syncLock;
public ConsoleAppender (@Nullable ILogFormatter formatter) {
super(formatter);
syncLock = new ReentrantLock();
}
public ConsoleAppender () {
this(null);
}
@Override
public void pushLogChecked (@Nonnull Log log) {
if (formatter == null) return;
syncLock.lock();
System.out.println(formatter.format(log));
syncLock.unlock();
}
}

View File

@ -9,28 +9,54 @@ public class SimpleFormatter implements ILogFormatter {
@Nonnull public String startTimestamp = "["; @Nonnull public String startTimestamp = "[";
@Nonnull public String endTimestamp = "]"; @Nonnull public String endTimestamp = "]";
@Nonnull public String startThreadName = "["; @Nonnull public String startThreadName = "[";
@Nonnull public String endThreadName = "]"; @Nonnull public String endThreadName = "]";
@Nonnull public String startCodePoint = "[";
@Nonnull public String endCodePoint = "]";
@Nonnull public String startLevelTag = "["; @Nonnull public String startLevelTag = "[";
@Nonnull public String endLevelTag = "]"; @Nonnull public String endLevelTag = "]";
public char followingLineFillChar = '\''; public char followingLineFillChar = '\'';
@Nonnull public String messageSeparator = " "; @Nonnull public String messageSeparator = " ";
@Nonnull @Nonnull
@Override @Override
public String format (@Nonnull Log log) { public String format (@Nonnull Log log) {
final StringBuilder message = new StringBuilder(); final StringBuilder message = new StringBuilder();
message.append(startTimestamp).append(log.timestamp).append(endTimestamp)
.append(startThreadName).append(log.thread.getName()).append(endThreadName); final String formattedTime = startTimestamp + this.formatTimestamp(log) + endTimestamp;
final String promptNewline = StringUtils.repeatChar(followingLineFillChar, message.length()); final String formattedThread = startThreadName + log.thread().getName() + endThreadName;
message.append(startLevelTag).append(log.level.tag()).append(endLevelTag) final String formattedCodePoint = startCodePoint + this.formatCodepoint(log) + endCodePoint;
.append(messageSeparator).append(log.message.message[0]); final String formattedLevel = startLevelTag + log.level().tag() + endLevelTag;
for (int i = 1; i < log.message.message.length; i++) {
message.append('\n').append(promptNewline) final String formattedPromptOnlyNewline = formattedTime + formattedCodePoint + formattedThread;
.append(startLevelTag).append(log.level.tag()).append(endLevelTag) final String formattedPrompt = formattedPromptOnlyNewline + formattedLevel;
.append(messageSeparator).append(log.message.message[i]); final String formattedPromptFollowing = StringUtils.repeatChar(followingLineFillChar, formattedPromptOnlyNewline.length()) + formattedLevel;
message.append(formattedPrompt)
.append(messageSeparator).append(log.message().getLines()[0]);
for (int i = 1; i < log.message().getLines().length; i++) {
message.append('\n').append(formattedPromptFollowing)
.append(messageSeparator).append(log.message().getLines()[i]);
} }
return message.toString(); return message.toString();
}
@Nonnull
protected String formatTimestamp (@Nonnull Log log) {
return String.valueOf(log.timestamp());
}
@Nonnull
protected String formatCodepoint (@Nonnull Log log) {
return log.stackTrace()[0].getClassName() + "#" + log.stackTrace()[0].getMethodName();
} }
} }

View File

@ -1,5 +1,7 @@
package cc.sukazyo.messiva.log; package cc.sukazyo.messiva.log;
import cc.sukazyo.messiva.log.message.IMessage;
import javax.annotation.Nonnull; import javax.annotation.Nonnull;
public interface ILogLevelImpl { public interface ILogLevelImpl {
@ -18,18 +20,18 @@ public interface ILogLevelImpl {
void fatal (@Nonnull String message); void fatal (@Nonnull String message);
void trace (@Nonnull Message message); void trace (@Nonnull IMessage message);
void debug (@Nonnull Message message); void debug (@Nonnull IMessage message);
void info (@Nonnull Message message); void info (@Nonnull IMessage message);
void warn (@Nonnull Message message); void warn (@Nonnull IMessage message);
void warning (@Nonnull Message message); void warning (@Nonnull IMessage message);
void error (@Nonnull Message message); void error (@Nonnull IMessage message);
void fatal (@Nonnull Message message); void fatal (@Nonnull IMessage message);
} }

View File

@ -3,16 +3,82 @@ package cc.sukazyo.messiva.log;
import javax.annotation.Nonnegative; import javax.annotation.Nonnegative;
import javax.annotation.Nonnull; import javax.annotation.Nonnull;
import cc.sukazyo.messiva.utils.StackUtils; import cc.sukazyo.messiva.log.message.IMessage;
import cc.sukazyo.std.contexts.GivenContext;
import cc.sukazyo.std.stacks.StacksExtensions;
import cc.sukazyo.std.stacks.WithCurrentStack;
import java.util.Arrays;
import java.util.function.Consumer;
public class Log { public class Log {
@Nonnull public final Message message;
@Nonnull public final ILogLevel level;
public final long timestamp;
@Nonnull public final Thread thread;
@Nonnull public final StackTraceElement[] stackTrace;
public static class Initializing {}
public static class StackOffset {
public int value = 0;
}
/** @deprecated Will be private. Use method with the same name instead. */
@Deprecated @Nonnull public final IMessage message;
/** @deprecated Will be private. Use method with the same name instead. */
@Deprecated @Nonnull public final ILogLevel level;
/** @deprecated Will be private. Use method with the same name instead. */
@Deprecated public final long timestamp;
/** @deprecated Will be private. Use method with the same name instead. */
@Deprecated @Nonnull public final Thread thread;
/** @deprecated Will be private. Use method with the same name instead. */
@Deprecated @Nonnull public final StackTraceElement[] stackTrace;
@Nonnull private final GivenContext context = GivenContext.apply();
{ context.ownedBy(Initializing.class).provide(StackOffset.class, new StackOffset()); }
@Nonnull public IMessage message () { return this.message; }
@Nonnull public ILogLevel level () { return this.level; }
@Nonnegative public long timestamp () { return this.timestamp; }
@Nonnull public Thread thread () { return this.thread; }
@Nonnull public StackTraceElement[] stackTrace () {
return Arrays.copyOfRange(
this.stackTrace,
this.context.ownedBy(Initializing.class).getUnsafe(StackOffset.class).value,
this.stackTrace.length
);
}
@Nonnull public GivenContext context () { return this.context; }
private Log (
@Nonnull IMessage message, @Nonnull ILogLevel level,
@Nonnull Thread thread, long timestamp, @Nonnull StackTraceElement[] stackTrace
) {
this.message = message;
this.level = level;
this.thread = thread;
this.timestamp = timestamp;
this.stackTrace = stackTrace;
}
public Log (
@Nonnull IMessage message, @Nonnull ILogLevel level,
@Nonnull Thread thread, long timestamp
) {
this(
message, level, thread, timestamp,
StacksExtensions.dropWhileClass(Log.class, WithCurrentStack.getStackTrace(0))
);
}
public Log (@Nonnull IMessage message, @Nonnull ILogLevel level, @Nonnull Thread thread) {
this(message, level, thread, System.currentTimeMillis());
}
public Log (@Nonnull IMessage message, @Nonnull ILogLevel level) {
this(message, level, Thread.currentThread());
}
/// #block old_constructors
/// for old Message
@Deprecated
public Log (@Nonnull Message message, @Nonnull ILogLevel level, public Log (@Nonnull Message message, @Nonnull ILogLevel level,
@Nonnull Thread thread,long timestamp, @Nonnull StackTraceElement[] stackTrace) { @Nonnull Thread thread,long timestamp, @Nonnull StackTraceElement[] stackTrace) {
this.message = message; this.message = message;
@ -22,35 +88,47 @@ public class Log {
this.stackTrace = stackTrace; this.stackTrace = stackTrace;
} }
@Deprecated
public Log (@Nonnegative int stackOffset, public Log (@Nonnegative int stackOffset,
@Nonnull Message message, @Nonnull ILogLevel level, @Nonnull Message message, @Nonnull ILogLevel level,
@Nonnull Thread thread, long timestamp) { @Nonnull Thread thread, long timestamp) {
this(message, level, thread, timestamp, StackUtils.getStackTrace(stackOffset+1)); this(message, level, thread, timestamp, WithCurrentStack.getStackTrace(stackOffset+2));
} }
@Deprecated
public Log (@Nonnull Message message, @Nonnull ILogLevel level, public Log (@Nonnull Message message, @Nonnull ILogLevel level,
@Nonnull Thread thread, long timestamp) { @Nonnull Thread thread, long timestamp) {
this(1, message, level, thread, timestamp); this(1, message, level, thread, timestamp);
} }
@Deprecated
public Log (@Nonnegative int stackOffset, public Log (@Nonnegative int stackOffset,
@Nonnull Message message, @Nonnull ILogLevel level, @Nonnull Message message, @Nonnull ILogLevel level,
@Nonnull Thread thread) { @Nonnull Thread thread) {
this(stackOffset+1, message, level, thread, System.currentTimeMillis()); this(stackOffset+1, message, level, thread, System.currentTimeMillis());
} }
@Deprecated
public Log (@Nonnull Message message, @Nonnull ILogLevel level, public Log (@Nonnull Message message, @Nonnull ILogLevel level,
@Nonnull Thread thread) { @Nonnull Thread thread) {
this(1, message, level, thread); this(1, message, level, thread);
} }
public Log (@Nonnegative int stackOffset, @Deprecated
@Nonnull Message message, @Nonnull ILogLevel level) { public Log( @Nonnegative int stackOffset, @Nonnull Message message, @Nonnull ILogLevel level){
this(stackOffset + 1, message, level, Thread.currentThread()); this(stackOffset + 1, message, level, Thread.currentThread());
} }
@Deprecated
public Log (@Nonnull Message message, @Nonnull ILogLevel level) { public Log (@Nonnull Message message, @Nonnull ILogLevel level) {
this(1, message, level); this(1, message, level);
} }
/// #endblock old_constructors
public Log withContext (@Nonnull Consumer<GivenContext> consumer) {
consumer.accept(this.context());
return this;
}
} }

View File

@ -1,15 +1,26 @@
package cc.sukazyo.messiva.log; package cc.sukazyo.messiva.log;
import cc.sukazyo.messiva.log.message.TextMessage;
import javax.annotation.Nonnull; import javax.annotation.Nonnull;
import javax.annotation.Nullable; import javax.annotation.Nullable;
public class Message { /**
* @deprecated API changed. Use {@link cc.sukazyo.messiva.log.message.IMessage} or {@link TextMessage}
* instead.
*/
@Deprecated
public class Message extends TextMessage {
/**
* Due to API changes, this field is no longer used and will always be empty.
*/
@Nonnull @Nonnull
public final String[] message; @Deprecated
public final String[] message = new String[]{};
public Message(@Nullable String message) { public Message(@Nullable String message) {
this.message = message == null ? new String[]{} : message.split("\n"); super(message);
} }
} }

View File

@ -0,0 +1,15 @@
package cc.sukazyo.messiva.log.message;
import java.util.Arrays;
public interface IMessage {
default String getText () {
return Arrays.stream(getLines())
.reduce((a, b) -> a + "\n" + b)
.orElse("");
}
String[] getLines ();
}

View File

@ -0,0 +1,23 @@
package cc.sukazyo.messiva.log.message;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
public class TextMessage implements IMessage {
@Nonnull String[] message;
public TextMessage (@Nullable String message) {
this.message = message != null ? message.split("\\n") : new String[]{};
}
public TextMessage (@Nonnull String[] messageLines) {
this.message = messageLines;
}
@Override
public String[] getLines () {
return message;
}
}

View File

@ -1,28 +1,49 @@
package cc.sukazyo.messiva.logger; package cc.sukazyo.messiva.logger;
import cc.sukazyo.messiva.appender.AppenderProvider;
import cc.sukazyo.messiva.component.LevelRestrictComponent; import cc.sukazyo.messiva.component.LevelRestrictComponent;
import cc.sukazyo.messiva.appender.IAppender; import cc.sukazyo.messiva.appender.IAppender;
import cc.sukazyo.messiva.log.*; import cc.sukazyo.messiva.log.*;
import cc.sukazyo.messiva.log.message.IMessage;
import cc.sukazyo.messiva.log.message.TextMessage;
import cc.sukazyo.std.contexts.GivenContext;
import cc.sukazyo.std.stacks.WithCurrentStack;
import scala.reflect.ClassTag;
import javax.annotation.Nonnull; import javax.annotation.Nonnull;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.stream.Collectors;
public class Logger implements ILogLevelImpl { public class Logger implements ILogLevelImpl {
@Nonnull public final List<IAppender> appends; /**
* @deprecated API changes. use {@link #appenderProviders} instead. This field will always
* be empty.
*/
@Deprecated
@Nonnull public final List<IAppender> appends = Collections.emptyList();
@Nonnull public final String name;
@Nonnull public final List<AppenderProvider> appenderProviders;
@Nonnull public LevelRestrictComponent levelSetting; @Nonnull public LevelRestrictComponent levelSetting;
public Logger () { public Logger () {
levelSetting = new LevelRestrictComponent(); levelSetting = new LevelRestrictComponent();
appends = new ArrayList<>(); appenderProviders = new ArrayList<>();
this.name = WithCurrentStack.getStackTrace(1)[0].getClass().getSimpleName();
} }
public Logger (@Nonnull IAppender... appends) { public Logger (@Nonnull IAppender... appends) {
this(); this();
this.appends.addAll(Arrays.asList(appends)); this.appenderProviders.addAll(
Arrays.stream(appends)
.map(IAppender::asProvider)
.collect(Collectors.toList())
);
} }
@Nonnull @Nonnull
@ -38,74 +59,87 @@ public class Logger implements ILogLevelImpl {
} }
public void trace (@Nonnull String message) { public void trace (@Nonnull String message) {
pushToAllAppender(new Log(1, new Message(message), LogLevels.TRACE)); pushToAllAppender(createNewLog(new TextMessage(message), LogLevels.TRACE));
} }
public void debug (@Nonnull String message) { public void debug (@Nonnull String message) {
pushToAllAppender(new Log(1, new Message(message), LogLevels.DEBUG)); pushToAllAppender(createNewLog(new TextMessage(message), LogLevels.DEBUG));
} }
public void info (@Nonnull String message) { public void info (@Nonnull String message) {
pushToAllAppender(new Log(1, new Message(message), LogLevels.INFO)); pushToAllAppender(createNewLog(new TextMessage(message), LogLevels.INFO));
} }
public void warn (@Nonnull String message) { public void warn (@Nonnull String message) {
pushToAllAppender(new Log(1, new Message(message), LogLevels.WARN)); pushToAllAppender(createNewLog(new TextMessage(message), LogLevels.WARN));
} }
public void warning (@Nonnull String message) { public void warning (@Nonnull String message) {
pushToAllAppender(new Log(1, new Message(message), LogLevels.WARN)); pushToAllAppender(createNewLog(new TextMessage(message), LogLevels.WARN));
} }
public void error (@Nonnull String message) { public void error (@Nonnull String message) {
pushToAllAppender(new Log(1, new Message(message), LogLevels.ERROR)); pushToAllAppender(createNewLog(new TextMessage(message), LogLevels.ERROR));
} }
public void fatal (@Nonnull String message) { public void fatal (@Nonnull String message) {
pushToAllAppender(new Log(1, new Message(message), LogLevels.FATAL)); pushToAllAppender(createNewLog(new TextMessage(message), LogLevels.FATAL));
} }
public void trace (@Nonnull Message message) { public void trace (@Nonnull IMessage message) {
pushToAllAppender(new Log(1, message, LogLevels.TRACE)); pushToAllAppender(createNewLog(message, LogLevels.TRACE));
} }
public void debug (@Nonnull Message message) { public void debug (@Nonnull IMessage message) {
pushToAllAppender(new Log(1, message, LogLevels.DEBUG)); pushToAllAppender(createNewLog(message, LogLevels.DEBUG));
} }
public void info (@Nonnull Message message) { public void info (@Nonnull IMessage message) {
pushToAllAppender(new Log(1, message, LogLevels.INFO)); pushToAllAppender(createNewLog(message, LogLevels.INFO));
} }
public void warn (@Nonnull Message message) { public void warn (@Nonnull IMessage message) {
pushToAllAppender(new Log(1, message, LogLevels.WARN)); pushToAllAppender(createNewLog(message, LogLevels.WARN));
} }
public void warning (@Nonnull Message message) { public void warning (@Nonnull IMessage message) {
pushToAllAppender(new Log(1, message, LogLevels.WARN)); pushToAllAppender(createNewLog(message, LogLevels.WARN));
} }
public void error (@Nonnull Message message) { public void error (@Nonnull IMessage message) {
pushToAllAppender(new Log(1, message, LogLevels.ERROR)); pushToAllAppender(createNewLog(message, LogLevels.ERROR));
} }
public void fatal (@Nonnull Message message) { public void fatal (@Nonnull IMessage message) {
pushToAllAppender(new Log(1, message, LogLevels.FATAL)); pushToAllAppender(createNewLog(message, LogLevels.FATAL));
} }
public void log (@Nonnull String message, @Nonnull ILogLevel level) { public void log (@Nonnull String message, @Nonnull ILogLevel level) {
pushToAllAppender(new Log(1, new Message(message), level)); pushToAllAppender(createNewLog(new TextMessage(message), level));
} }
public void log (@Nonnull Message message, @Nonnull ILogLevel level) { public void log (@Nonnull IMessage message, @Nonnull ILogLevel level) {
pushToAllAppender(new Log(1, message, level)); pushToAllAppender(createNewLog(message, level));
}
protected Log createNewLog (@Nonnull IMessage message, ILogLevel level) {
return new Log(message, level)
.withContext(cxt -> cxt.ownedBy(Log.Initializing.class).getUnsafe(Log.StackOffset.class).value+=2);
} }
protected void pushToAllAppender (@Nonnull Log log) { protected void pushToAllAppender (@Nonnull Log log) {
if (!levelSetting.checkLevel(log.level)) return; if (!levelSetting.checkLevel(log.level())) return;
for (IAppender appender : appends) { this.findAppends(log)
appender.pushLog(log); .forEach(appender -> appender.pushLog(log));
} }
protected List<IAppender> findAppends (@Nonnull Log log) {
final GivenContext context = GivenContext.apply();
context.provide(this, ClassTag.apply(AppenderProvider.class));
context.provide(log, ClassTag.apply(Log.class));
return this.appenderProviders.stream()
.flatMap(provider -> provider.findAppends(context).stream())
.collect(Collectors.toList());
} }
} }

View File

@ -1,14 +0,0 @@
package cc.sukazyo.messiva.utils;
import java.util.Arrays;
public class StackUtils {
public static StackTraceElement[] getStackTrace (int offset) {
offset += 2;
StackTraceElement[] origins = Thread.currentThread().getStackTrace();
origins = Arrays.copyOfRange(origins, offset, origins.length);
return origins;
}
}