forked from wttech/gradle-aem-plugin
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathInstanceTailer.kt
163 lines (129 loc) · 5.37 KB
/
InstanceTailer.kt
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
package com.cognifide.gradle.aem.instance.tail
import com.cognifide.gradle.aem.AemExtension
import com.cognifide.gradle.aem.AemTask
import com.cognifide.gradle.aem.common.instance.Instance
import com.cognifide.gradle.aem.common.utils.Formats
import com.cognifide.gradle.aem.instance.tail.io.FileDestination
import com.cognifide.gradle.aem.instance.tail.io.LogFiles
import com.cognifide.gradle.aem.instance.tail.io.UrlSource
import java.io.File
import java.nio.file.Paths
import kotlin.math.max
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.delay
import kotlinx.coroutines.isActive
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
class InstanceTailer(val aem: AemExtension) {
/**
* Directory where log files will be stored.
*/
var rootDir: File = AemTask.temporaryDir(aem.project, "instanceTail")
/**
* Instances from which logs will be tailed.
*/
var instances: List<Instance> = listOf()
/**
* Determines log file being tracked on AEM instance.
*/
var logFilePath = aem.props.string("instance.tail.logFilePath") ?: "/logs/error.log"
/**
* Hook for tracking all log entries on each AEM instance.
*
* Useful for integrating external services like chats etc.
*/
var logListener: Log.(Instance) -> Unit = {}
/**
* Log filter responsible for filtering incidents.
*/
val logFilter = LogFilter()
fun logFilter(options: LogFilter.() -> Unit) {
logFilter.apply(options)
}
/**
* Determines which log entries are considered as incidents.
*/
var incidentChecker: Log.(Instance) -> Boolean = { instance ->
val levels = Formats.toList(instance.property("instance.tail.incidentLevels"))
?: aem.props.list("instance.tail.incidentLevels")
?: INCIDENT_LEVELS_DEFAULT
val oldMillis = instance.property("instance.tail.incidentOld")?.toLong()
?: aem.props.long("instance.tail.incidentOld")
?: INCIDENT_OLD_DEFAULT
isLevel(levels) && !isOlderThan(instance, oldMillis) && !logFilter.isExcluded(this)
}
/**
* Path to file holding wildcard rules that will effectively deactivate notifications for desired exception.
*
* Changes in that file are automatically considered (tailer restart is not required).
*/
var incidentFilter: File = aem.props.string("instance.tail.incidentFilter")?.let { aem.project.file(it) }
?: File(aem.configCommonDir, "instanceTail/incidentFilter.txt")
/**
* Time window in which exceptions will be aggregated and reported as single incident.
*/
var incidentDelay = aem.props.long("instance.tail.incidentDelay") ?: 5000L
/**
* Determines how often logs will be polled from AEM instance.
*/
var fetchInterval = aem.props.long("instance.tail.fetchInterval") ?: 500L
var lockInterval = aem.props.long("instance.tail.lockInterval") ?: max(1000L + fetchInterval, 2000L)
var linesChunkSize = aem.props.long("instance.tail.linesChunkSize") ?: 400L
// https://sridharmandra.blogspot.com/2016/08/tail-aem-logs-in-browser.html
fun errorLogEndpoint(instance: Instance): String {
val fileName = logFilePath.replace("/", "%2F")
val path = when {
Formats.versionAtLeast(instance.version, "6.2.0") -> ENDPOINT_PATH
else -> ENDPOINT_PATH_OLD
}
return "$path?tail=$linesChunkSize&name=$fileName"
}
val logFile: String
get() = Paths.get(logFilePath).fileName.toString()
fun incidentFilter(options: LogFilter.() -> Unit) {
logFilter.apply(options)
}
private val logFiles = LogFiles(this)
fun tail() {
checkStartLock()
runBlocking {
startAll().forEach { tailer ->
launch {
while (isActive) {
logFiles.lock()
tailer.tail()
delay(fetchInterval)
}
}
}
}
}
private fun checkStartLock() {
if (logFiles.isLocked()) {
throw InstanceTailerException("Another instance of log tailer is running for this project.")
}
logFiles.lock()
}
private fun startAll(): List<LogTailer> {
val notificationChannel = Channel<LogChunk>(Channel.UNLIMITED)
val logNotifier = LogNotifier(notificationChannel, aem.notifier, logFiles)
logNotifier.listenTailed()
return instances.map { start(it, notificationChannel) }
}
private fun start(instance: Instance, notificationChannel: Channel<LogChunk>): LogTailer {
val source = UrlSource(this, instance)
val destination = FileDestination(instance.name, logFiles)
val logAnalyzerChannel = Channel<Log>(Channel.UNLIMITED)
val logAnalyzer = InstanceAnalyzer(this, instance, logAnalyzerChannel, notificationChannel)
logAnalyzer.listenTailed()
val logFile = logFiles.main(instance.name)
aem.logger.lifecycle("Tailing logs to file: $logFile")
return LogTailer(source, destination, logAnalyzerChannel)
}
companion object {
const val ENDPOINT_PATH = "/system/console/slinglog/tailer.txt"
const val ENDPOINT_PATH_OLD = "/bin/crxde/logs"
val INCIDENT_LEVELS_DEFAULT = listOf("ERROR")
const val INCIDENT_OLD_DEFAULT = 1000L * 10
}
}