Skip to content

Commit 3e86509

Browse files
committed
1. Introduced automatic watch reactivation on log changes and server restarts
2. Prevented records doubling on sequential watching toggling 3. Tuned visual look of composite records 4. Lifted composite logs to the top of choice list 5. Changed christmas substrate to classic one
1 parent 88c2231 commit 3e86509

20 files changed

+146
-83
lines changed

build.gradle

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ repositories {
4141

4242
dependencies {
4343
// Backend compile deps
44-
compile("org.springframework.boot:spring-boot-devtools:${springBootVersion}")
44+
// compile("org.springframework.boot:spring-boot-devtools:${springBootVersion}")
4545
compile("org.springframework.boot:spring-boot-starter-web:${springBootVersion}")
4646
compile("org.springframework.boot:spring-boot-starter-actuator:${springBootVersion}")
4747
compile("org.springframework.boot:spring-boot-starter-websocket:${springBootVersion}")

config/application-win.yaml

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,9 @@ choices:
1818
- group: MT
1919
pathBase: C:\lang\analog\log-samples\
2020
plainLogs:
21-
- core.log as "$f - $g"
21+
- core.log as "static $f - $g"
2222
- pppinfo.log as "$f - $g"
23+
- generated\core.log as "generated $f - $g"
2324
compositeLogs:
2425
- path: generated\bankplus.log
2526
node: angara
@@ -44,10 +45,7 @@ choices:
4445
compositeLogs:
4546
- path: bankplus.log
4647
title: $f - $g
47-
selected: false
48-
encoding: CP-1251
4948
timestamp: dd.MM.yy HH:mm:ss,SSS
5049
- path: logSample.log
5150
title: $f - $g
52-
selected: false
5351
timestamp: dd.MM.yy HH:mm:ss,SSS

src/main/java/tech/toparvion/analog/model/TrackingRequest.java

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,14 @@ public class TrackingRequest implements Serializable {
1616
private final String timestampFormat;
1717
private final String nodeName;
1818
private final String uid;
19+
private final boolean isTailNeeded;
1920

20-
public TrackingRequest(String logFullPath, @Nullable String timestampFormat, String nodeName, String uid) {
21+
public TrackingRequest(String logFullPath, @Nullable String timestampFormat, String nodeName, String uid, boolean isTailNeeded) {
2122
this.logFullPath = logFullPath;
2223
this.timestampFormat = timestampFormat;
2324
this.nodeName = nodeName;
2425
this.uid = uid;
26+
this.isTailNeeded = isTailNeeded;
2527
}
2628

2729
public String getLogFullPath() {
@@ -42,6 +44,10 @@ public String getUid() {
4244
return uid;
4345
}
4446

47+
public boolean isTailNeeded() {
48+
return isTailNeeded;
49+
}
50+
4551
/**
4652
* A request with no timestamp format is considered 'plain' as it cannot be involved into complex aggregating
4753
* tracking logic and thus is suitable for plain old tracking only.
@@ -58,6 +64,7 @@ public String toString() {
5864
", timestampFormat='" + timestampFormat + '\'' +
5965
", nodeName='" + nodeName + '\'' +
6066
", uid='" + uid + '\'' +
67+
", isTailNeeded=" + isTailNeeded +
6168
'}';
6269
}
6370

src/main/java/tech/toparvion/analog/remote/agent/TailingFlowProvider.java

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -53,9 +53,10 @@ public TailingFlowProvider(TimestampExtractor timestampExtractor,
5353
* The core method for building AnaLog dynamic behavior. Creates and returns an integration flow for watching given
5454
* log. No duplicate flow checking is done inside.
5555
* @param logPath full path to log file to tail
56+
* @param isTailNeeded should 'tail' include last several lines of the log?
5657
* @return a new tailing flow
5758
*/
58-
IntegrationFlow provideAggregatingFlow(String logPath) {
59+
IntegrationFlow provideAggregatingFlow(String logPath, boolean isTailNeeded) {
5960
// each tailing flow must have its own instance of correlationProvider as it is stateful and not thread-safe
6061
CorrelationIdHeaderEnricher correlationProvider = new CorrelationIdHeaderEnricher();
6162
// each tailing flow must have its own instance of sequenceProvider as it is stateful and not thread-safe
@@ -79,7 +80,7 @@ sequence number (much like the one built in FileSplitter) and provided by dedica
7980
return IntegrationFlows
8081
.from(tailAdapter(new File(logPath))
8182
.id("tailSource:"+logPath)
82-
.nativeOptions(tailSpecificsProvider.getCompositeTailNativeOptions())
83+
.nativeOptions(tailSpecificsProvider.getCompositeTailNativeOptions(isTailNeeded))
8384
.fileDelay(tailSpecificsProvider.getAttemptsDelay())
8485
.enableStatusReader(true)) // to receive events of log rotation, etc.
8586
.enrichHeaders(e -> e.headerFunction(LOG_TIMESTAMP_VALUE__HEADER, timestampExtractor::extractTimestamp))
@@ -92,11 +93,11 @@ sequence number (much like the one built in FileSplitter) and provided by dedica
9293
.get();
9394
}
9495

95-
IntegrationFlow providePlainFlow(String logPath) {
96+
IntegrationFlow providePlainFlow(String logPath, boolean isTailNeeded) {
9697
return IntegrationFlows
9798
.from(tailAdapter(new File(logPath))
9899
.id("tailSource:"+logPath)
99-
.nativeOptions(tailSpecificsProvider.getPlainTailNativeOptions())
100+
.nativeOptions(tailSpecificsProvider.getPlainTailNativeOptions(isTailNeeded))
100101
.fileDelay(tailSpecificsProvider.getAttemptsDelay())
101102
.enableStatusReader(true)) // to receive events log rotation, etc.
102103
.aggregate(aggregatorSpec -> aggregatorSpec

src/main/java/tech/toparvion/analog/remote/agent/TrackingService.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -74,8 +74,8 @@ public TrackingService(@SuppressWarnings("SpringJavaInjectionPointsAutowiringIns
7474
/**
7575
* Initiates tracking process for the log specified in {@code request}: <ol>
7676
* <li>Checks if this is a duplicate registration (the request for the same log came from the same watcher);</li>
77-
* <li>Finds or {@link TailingFlowProvider#provideAggregatingFlow(java.lang.String) creates} tailing flow (alongside
78-
* with the {@link TimestampExtractor#registerNewTimestampFormat(java.lang.String, java.lang.String) registration}
77+
* <li>Finds or {@link TailingFlowProvider#provideAggregatingFlow(String, boolean) creates} tailing flow (alongside
78+
* with the {@link TimestampExtractor#registerNewTimestampFormat(String, String) registration}
7979
* of the specified timestamp format);</li>
8080
* <li>Creates new {@code RmiOutboundGateway} capable of sending messages to the {@code watcherAddress} and makes
8181
* it a subscriber for the tailing flow output channel;</li>
@@ -110,7 +110,7 @@ void registerWatcher(TrackingRequest request, InetSocketAddress watcherAddress)
110110

111111
} else if (!request.isPlain()) {
112112
IntegrationFlowRegistration trackingRegistration = flowContext
113-
.registration(trackingFlowProvider.provideAggregatingFlow(logPath))
113+
.registration(trackingFlowProvider.provideAggregatingFlow(logPath, request.isTailNeeded()))
114114
.autoStartup(true)
115115
.register();
116116
trackingRegistry.put(logPath, trackingRegistration.getId());
@@ -121,7 +121,7 @@ void registerWatcher(TrackingRequest request, InetSocketAddress watcherAddress)
121121

122122
} else {
123123
IntegrationFlowRegistration trackingRegistration = flowContext
124-
.registration(trackingFlowProvider.providePlainFlow(logPath))
124+
.registration(trackingFlowProvider.providePlainFlow(logPath, request.isTailNeeded()))
125125
.autoStartup(true)
126126
.register();
127127
trackingRegistry.put(logPath, trackingRegistration.getId());

src/main/java/tech/toparvion/analog/remote/server/MetaDataSender.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ public MetaDataSender(SimpMessagingTemplate messagingTemplate, TailSpecificsProv
3535
this.tailSpecificsProvider = tailSpecificsProvider;
3636
}
3737

38-
public void sendMetaData(Message<?> metaMessage) {
38+
void sendMetaData(Message<?> metaMessage) {
3939
// extract header values in order to include them into metadata being sent
4040
String uid = metaMessage.getHeaders().get(LOG_CONFIG_ENTRY_UID__HEADER, String.class);
4141
String sourceNode = metaMessage.getHeaders().get(SOURCE_NODE__HEADER, String.class);

src/main/java/tech/toparvion/analog/remote/websocket/WebSocketEventListener.java

Lines changed: 39 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -72,10 +72,8 @@ public void onConnected(SessionConnectedEvent event) {
7272
public void onSubscribe(SessionSubscribeEvent event) {
7373
// First let's extract all the necessary info about new watching from the subscribe request
7474
StompHeaderAccessor headers = StompHeaderAccessor.wrap(event.getMessage());
75-
List<String> rawHeaderValue = headers.getNativeHeader("isPlain");
76-
assert (rawHeaderValue != null) && (rawHeaderValue.size() == 1)
77-
: "'isPlain' header of SUBSCRIBE command is absent or malformed";
78-
Boolean isPlain = Boolean.valueOf(rawHeaderValue.get(0));
75+
boolean isPlain = getBooleanNativeHeader(headers, "isPlain");
76+
boolean isTailNeeded = getBooleanNativeHeader(headers, "isTailNeeded");
7977
String sessionId = headers.getSessionId();
8078
String destination = headers.getDestination();
8179
assert (destination != null) && destination.startsWith(WEBSOCKET_TOPIC_PREFIX)
@@ -98,7 +96,7 @@ public void onSubscribe(SessionSubscribeEvent event) {
9896
// 1. Ensure that all RMI registration channels are created
9997
ensureRegistrationChannelsCreated(logConfig);
10098
// 2. Register the tracking on specified nodes
101-
switchTracking(logConfig, true);
99+
startTrackingOnServer(logConfig, isTailNeeded);
102100
// 3. Remember the tracking in the registry
103101
registry.addEntry(logConfig, sessionId);
104102
log.info("New tracking for log '{}' has started within session id={}.", logConfig.getUid(), sessionId);
@@ -108,6 +106,9 @@ public void onSubscribe(SessionSubscribeEvent event) {
108106
ServerFailure failure = new ServerFailure(e.getMessage(), now());
109107
messagingTemplate.convertAndSend(WEBSOCKET_TOPIC_PREFIX + logConfig.getUid(),
110108
failure, singletonMap(MESSAGE_TYPE_HEADER, MessageType.FAILURE));
109+
// TODO Научиться по аналогии с этим отправлять сообщение об успешной настройке подписки, для чего превратить
110+
// serverFailure в более общий тип сообщения. При получении этого типа убирать на клиенте прелоадер,
111+
// выставленный перед отправкой запроса на подписку.
111112
}
112113

113114
} else { // i.e. if there are watching sessions already in registry
@@ -141,11 +142,11 @@ private void stopTrackingIfNeeded(Message<byte[]> message, boolean isUnsubscribi
141142
// check if there is no such session(s)
142143
if (fellows == null) {
143144
if (isUnsubscribing) { // in case of unsubscribing it is incorrect situation
144-
throw new IllegalStateException("No registered session(s) found for sessionId=" + sessionId);
145+
log.warn("No registered session(s) found for sessionId={}", sessionId);
145146
} else { // but in case of disconnecting it is quite right
146147
log.info("No registered session found for sessionId={}.", sessionId);
147-
return;
148148
}
149+
return;
149150
}
150151
// check if there any other session(s) left (may require synchronization on registry object)
151152
if (fellows.size() > 1) {
@@ -157,13 +158,30 @@ private void stopTrackingIfNeeded(Message<byte[]> message, boolean isUnsubscribi
157158
// in case it was the latest session watching that log we should stop the tracking
158159
LogConfigEntry watchingLog = registry.findLogConfigEntryBy(sessionId);
159160
log.debug("No sessions left watching log '{}'. Will deactivate the tracking...", watchingLog.getUid());
160-
switchTracking(watchingLog, false);
161+
stopTrackingOnServer(watchingLog);
161162
// now that the log is not tracked anymore we need to remove it from the registry
162163
registry.removeEntry(watchingLog);
163164
log.info("Current node has unregistered itself from tracking log '{}' as there is no watching sessions anymore.",
164165
watchingLog.getUid());
165166
}
166167

168+
private boolean getBooleanNativeHeader(StompHeaderAccessor headers, String name) {
169+
List<String> rawHeaderValue = headers.getNativeHeader(name);
170+
assert (rawHeaderValue != null) && (rawHeaderValue.size() == 1)
171+
: format("'%s' header of SUBSCRIBE command is absent or malformed", name);
172+
return Boolean.valueOf(rawHeaderValue.get(0));
173+
}
174+
175+
/**
176+
* When watching request comes for a plain log AnaLog does not tries to find corresponding log config entry.
177+
* Instead it just creates new ('artificial') entry and then works with it only. This approach allows AnaLog to
178+
* watch arbitrary plain logs independently of its configuration. Particularly, it means that a user can set any
179+
* path into AnaLog's address line and start to watch it the same way as if it was pre-configured as a choice
180+
* variant in AnaLog configuration.
181+
*
182+
* @param path full path of log file to watch for
183+
* @return newly created log config entry for the specified path
184+
*/
167185
@NotNull
168186
private LogConfigEntry createPlainLogConfigEntry(String path) {
169187
LogConfigEntry artificialEntry = new LogConfigEntry();
@@ -206,13 +224,22 @@ private void ensureRegistrationChannelsCreated(LogConfigEntry matchingEntry) {
206224
});
207225
}
208226

209-
private void switchTracking(LogConfigEntry logConfigEntry, boolean isOn) {
227+
private void stopTrackingOnServer(LogConfigEntry logConfigEntry) {
228+
switchTrackingOnServer(logConfigEntry, false, false);
229+
}
230+
231+
private void startTrackingOnServer(LogConfigEntry logConfigEntry, boolean isTailNeeded) {
232+
switchTrackingOnServer(logConfigEntry, true, isTailNeeded);
233+
}
234+
235+
private void switchTrackingOnServer(LogConfigEntry logConfigEntry, boolean isOn, boolean isTailNeeded) {
236+
assert !(isTailNeeded && !isOn) : "isTailNeeded flag should not be raised when switching the tracking off";
210237
ClusterNode myselfNode = clusterProperties.getMyselfNode();
211238
String fullPath = buildFullPath(logConfigEntry);
212239
String nodeName = nvls(logConfigEntry.getNode(), myselfNode.getName());
213240

214241
// send registration request for the main entry
215-
TrackingRequest primaryRequest = new TrackingRequest(fullPath, logConfigEntry.getTimestamp(), nodeName, logConfigEntry.getUid());
242+
TrackingRequest primaryRequest = new TrackingRequest(fullPath, logConfigEntry.getTimestamp(), nodeName, logConfigEntry.getUid(), isTailNeeded);
216243
log.debug("Switching {} the registration by PRIMARY request: {}", isOn ? "ON":"OFF", primaryRequest);
217244
remoteGateway.switchRegistration(primaryRequest, isOn);
218245

@@ -224,7 +251,8 @@ private void switchTracking(LogConfigEntry logConfigEntry, boolean isOn) {
224251
normalizePath(included.getPath()),
225252
included.getTimestamp(),
226253
nvls(included.getNode(), myselfNode.getName()),
227-
logConfigEntry.getUid());
254+
logConfigEntry.getUid(),
255+
isTailNeeded);
228256
log.debug("Switching {} the registration by INCLUDED request: {}", isOn ? "ON":"OFF", includedRequest);
229257
remoteGateway.switchRegistration(includedRequest, isOn);
230258

src/main/java/tech/toparvion/analog/service/LogChoicesProvider.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,8 +50,8 @@ List<LogChoice> provideLogChoices() {
5050

5151
private Stream<LogChoice> flattenGroup(ChoiceGroup group) {
5252
Set<LogChoice> choices = new LinkedHashSet<>();
53-
choices.addAll(processPlainLogs(group));
5453
choices.addAll(processCompositeLogs(group));
54+
choices.addAll(processPlainLogs(group));
5555
choices.addAll(processScanDir(group));
5656
return choices.stream();
5757
}

src/main/java/tech/toparvion/analog/service/tail/GnuCoreUtilsTailSpecificsProvider.java

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,13 +21,15 @@ public static boolean matches(String idString) {
2121
}
2222

2323
@Override
24-
public String getCompositeTailNativeOptions() {
25-
return "-F -n 20"; // follow file name with retries, starting with the very end of the file
24+
public String getCompositeTailNativeOptions(boolean includePreviousLines) {
25+
// follow file name with retries, starting with either the very EOF or some offset before the EOF
26+
return "-F -n " + (includePreviousLines ? "20" : "0");
2627
}
2728

2829
@Override
29-
public String getPlainTailNativeOptions() {
30-
return "-F -n 45"; // follow file name with retries, starting from some offset before the EOF
30+
public String getPlainTailNativeOptions(boolean includePreviousLines) {
31+
// follow file name with retries, starting with either the very EOF or some offset before the EOF
32+
return "-F -n " + (includePreviousLines ? "45" : "0");
3133
}
3234

3335
@Override

src/main/java/tech/toparvion/analog/service/tail/SolarisTailSpecificsProvider.java

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
import tech.toparvion.analog.model.TailEventType;
44

5+
import static java.lang.String.format;
6+
57
/**
68
* tail's specifics for Solaris OS family. Actually there are different tail implementations for various Solaris
79
* distributions (SunOS, IllumOs, etc) but AnaLog doesn't need to distinguish them so far.
@@ -18,13 +20,13 @@ public static boolean matches(String idfString) {
1820
}
1921

2022
@Override
21-
public String getCompositeTailNativeOptions() {
22-
return "-20f"; // -F option is not supported on SunOS
23+
public String getCompositeTailNativeOptions(boolean includePreviousLines) {
24+
return format("-%sf", includePreviousLines?"20":"0"); // -F option is not supported on SunOS
2325
}
2426

2527
@Override
26-
public String getPlainTailNativeOptions() {
27-
return "-45f"; // -F option is not supported on SunOS
28+
public String getPlainTailNativeOptions(boolean includePreviousLines) {
29+
return format("-%sf", includePreviousLines?"45":"0"); // -F option is not supported on SunOS
2830
}
2931

3032
@Override

0 commit comments

Comments
 (0)