Skip to content

Commit 4ae0f1a

Browse files
authored
Feeds (#21)
* Add yaml config for feeds
1 parent 6e365c2 commit 4ae0f1a

File tree

15 files changed

+415
-153
lines changed

15 files changed

+415
-153
lines changed

json-log-viewer/js/src/main/scala/ru/d10xa/jsonlogviewer/shell/ShellImpl.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@ import fs2.*
55

66
import java.io.*
77

8-
class ShellImpl extends Shell {
8+
class ShellImpl[F[_]] extends Shell[F] {
99

10-
def mergeCommands(commands: List[String]): Stream[IO, String] = Stream.empty
10+
def mergeCommands(commands: List[String]): Stream[F, String] = Stream.empty
1111

1212
}

json-log-viewer/jvm/src/main/scala/ru/d10xa/jsonlogviewer/decline/ConfigInitImpl.scala

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import cats.effect.IO
55
import ru.d10xa.jsonlogviewer.ConfigYamlReader
66
import ru.d10xa.jsonlogviewer.decline.Config.FormatIn
77
import cats.syntax.all.*
8+
import ru.d10xa.jsonlogviewer.decline.yaml.ConfigYaml
89

910
import java.io.File
1011

Lines changed: 14 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,37 +1,35 @@
11
package ru.d10xa.jsonlogviewer.shell
22

33
import cats.effect.*
4-
import fs2.*
4+
import cats.syntax.all.*
55

6-
import java.io.*
6+
class ShellImpl[F[_]: Async] extends Shell[F] {
77

8-
class ShellImpl extends Shell {
9-
10-
def createProcess(command: String): Resource[IO, Process] =
11-
Resource.make(IO {
8+
def createProcess(command: String): Resource[F, Process] =
9+
Resource.make(Async[F].delay {
1210
new ProcessBuilder("sh", "-c", command)
1311
.redirectErrorStream(true)
1412
.start()
15-
})(process => IO(process.destroy()).void)
13+
})(process => Async[F].delay(process.destroy()))
1614

17-
def runInfiniteCommand(command: String): Stream[IO, String] =
18-
Stream.resource(createProcess(command)).flatMap { process =>
15+
def runInfiniteCommand(command: String): fs2.Stream[F, String] =
16+
fs2.Stream.resource(createProcess(command)).flatMap { process =>
1917
fs2.io
2018
.readInputStream(
21-
IO(process.getInputStream),
19+
Async[F].delay(process.getInputStream),
2220
4096,
2321
closeAfterUse = false
2422
)
25-
.through(text.utf8.decode)
26-
.through(text.lines)
27-
.onFinalize(IO {
23+
.through(fs2.text.utf8.decode)
24+
.through(fs2.text.lines)
25+
.onFinalize(Async[F].delay {
2826
process.waitFor()
2927
}.void)
3028
}
3129

32-
def mergeCommands(commands: List[String]): Stream[IO, String] = {
30+
def mergeCommands(commands: List[String]): fs2.Stream[F, String] = {
3331
val streams = commands.map(runInfiniteCommand)
34-
Stream.emits(streams).parJoin(math.max(1, commands.length))
32+
fs2.Stream.emits(streams).parJoin(math.max(1, commands.length))
3533
}
3634

37-
}
35+
}

json-log-viewer/shared/src/main/scala/ru/d10xa/jsonlogviewer/Application.scala

Lines changed: 5 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,17 @@
11
package ru.d10xa.jsonlogviewer
22

3-
import cats.data.Validated
43
import cats.effect.*
54
import com.monovore.decline.Opts
65
import com.monovore.decline.effect.CommandIOApp
76
import fs2.*
87
import fs2.io.*
98
import ru.d10xa.jsonlogviewer.decline.Config
109
import ru.d10xa.jsonlogviewer.decline.Config.FormatIn
11-
import ru.d10xa.jsonlogviewer.decline.DeclineOpts
12-
import ru.d10xa.jsonlogviewer.logfmt.LogfmtLogLineParser
13-
import _root_.io.circe.yaml.scalayaml.parser
14-
import cats.syntax.all.*
1510
import ru.d10xa.jsonlogviewer.decline.ConfigInit
1611
import ru.d10xa.jsonlogviewer.decline.ConfigInitImpl
17-
import ru.d10xa.jsonlogviewer.decline.ConfigYaml
12+
import ru.d10xa.jsonlogviewer.decline.DeclineOpts
13+
import ru.d10xa.jsonlogviewer.decline.yaml.ConfigYaml
14+
import ru.d10xa.jsonlogviewer.logfmt.LogfmtLogLineParser
1815
import ru.d10xa.jsonlogviewer.shell.ShellImpl
1916

2017
object Application
@@ -23,30 +20,13 @@ object Application
2320
"Print json logs in human-readable form"
2421
):
2522

26-
private val stdinLinesStream: Stream[IO, String] =
27-
stdinUtf8[IO](1024 * 1024 * 10)
28-
.repartition(s => Chunk.array(s.split("\n", -1)))
29-
.filter(_.nonEmpty)
30-
3123
private val configInit: ConfigInit = new ConfigInitImpl
3224

3325
def main: Opts[IO[ExitCode]] = DeclineOpts.config.map { c =>
3426
configInit.initConfig(c).flatMap { updatedConfig =>
3527
IO {
36-
val jsonPrefixPostfix = JsonPrefixPostfix(JsonDetector())
37-
val logLineParser = updatedConfig.formatIn match {
38-
case Some(FormatIn.Logfmt) => LogfmtLogLineParser(updatedConfig)
39-
case _ => JsonLogLineParser(updatedConfig, jsonPrefixPostfix)
40-
}
41-
val commandsOpt = updatedConfig.configYaml.flatMap(_.commands).filter(_.nonEmpty)
42-
val stream = commandsOpt match {
43-
case Some(cmds) if cmds.nonEmpty =>
44-
new ShellImpl().mergeCommands(cmds)
45-
case _ =>
46-
stdinLinesStream
47-
}
48-
stream
49-
.through(LogViewerStream.stream[IO](updatedConfig, logLineParser))
28+
LogViewerStream
29+
.stream[IO](updatedConfig)
5030
.through(text.utf8.encode)
5131
.through(io.stdout)
5232
.compile

json-log-viewer/shared/src/main/scala/ru/d10xa/jsonlogviewer/ConfigYamlReader.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@ package ru.d10xa.jsonlogviewer
22

33
import cats.effect.IO
44
import cats.data.ValidatedNel
5-
import ru.d10xa.jsonlogviewer.decline.ConfigYaml
6-
import ru.d10xa.jsonlogviewer.decline.ConfigYamlLoader
5+
import ru.d10xa.jsonlogviewer.decline.yaml.ConfigYaml
6+
import ru.d10xa.jsonlogviewer.decline.yaml.ConfigYamlLoader
77

88
import scala.io.Source
99

Lines changed: 93 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,115 @@
11
package ru.d10xa.jsonlogviewer
22

3+
import cats.effect.Async
4+
import cats.syntax.all.*
35
import fs2.*
46
import fs2.io.*
57
import ru.d10xa.jsonlogviewer.decline.Config
8+
import ru.d10xa.jsonlogviewer.decline.Config.FormatIn
9+
import ru.d10xa.jsonlogviewer.decline.ConfigInit
10+
import ru.d10xa.jsonlogviewer.decline.ConfigInitImpl
11+
import ru.d10xa.jsonlogviewer.decline.DeclineOpts
12+
import ru.d10xa.jsonlogviewer.decline.yaml.ConfigYaml
13+
import ru.d10xa.jsonlogviewer.decline.yaml.Feed
614
import ru.d10xa.jsonlogviewer.formatout.ColorLineFormatter
715
import ru.d10xa.jsonlogviewer.formatout.RawFormatter
16+
import ru.d10xa.jsonlogviewer.logfmt.LogfmtLogLineParser
17+
import ru.d10xa.jsonlogviewer.query.QueryAST
18+
import ru.d10xa.jsonlogviewer.shell.ShellImpl
19+
820
object LogViewerStream {
921

10-
def stream[F[_]](
22+
private def makeLogLineParser(
1123
config: Config,
12-
logLineParser: LogLineParser
13-
): Pipe[F, String, String] = stream =>
24+
optFormatIn: Option[FormatIn]
25+
): LogLineParser = {
26+
val jsonPrefixPostfix = JsonPrefixPostfix(JsonDetector())
27+
optFormatIn match {
28+
case Some(FormatIn.Logfmt) => LogfmtLogLineParser(config)
29+
case _ => JsonLogLineParser(config, jsonPrefixPostfix)
30+
}
31+
}
32+
33+
private def commandsToStream[F[_]: Async](
34+
commands: List[String]
35+
): Stream[F, String] = {
36+
new ShellImpl[F]().mergeCommands(commands)
37+
}
38+
39+
private def stdinLinesStream[F[_]: Async]: Stream[F, String] =
40+
stdinUtf8[F](1024 * 1024 * 10)
41+
.repartition(s => Chunk.array(s.split("\n", -1)))
42+
.filter(_.nonEmpty)
43+
44+
private def processStream[F[_]: Async](
45+
baseConfig: Config,
46+
lines: Stream[F, String],
47+
feedFilter: Option[QueryAST],
48+
feedFormatIn: Option[FormatIn],
49+
feedName: Option[String]
50+
): Stream[F, String] = {
51+
val effectiveFormatIn = feedFormatIn.orElse(baseConfig.formatIn)
52+
val effectiveFilter = feedFilter.orElse(baseConfig.filter)
53+
val effectiveConfig = baseConfig.copy(
54+
filter = effectiveFilter,
55+
formatIn = effectiveFormatIn
56+
)
57+
1458
val timestampFilter = TimestampFilter()
15-
val outputLineFormatter = config.formatOut match
59+
val parseResultKeys = ParseResultKeys(effectiveConfig)
60+
val logLineFilter = LogLineFilter(effectiveConfig, parseResultKeys)
61+
val logLineParser = makeLogLineParser(effectiveConfig, effectiveFormatIn)
62+
val outputLineFormatter = effectiveConfig.formatOut match
1663
case Some(Config.FormatOut.Raw) => RawFormatter()
17-
case Some(Config.FormatOut.Pretty) | None => ColorLineFormatter(config)
18-
19-
val parseResultKeys = ParseResultKeys(config)
20-
val logLineFilter = LogLineFilter(config, parseResultKeys)
21-
stream
64+
case Some(Config.FormatOut.Pretty) | None =>
65+
ColorLineFormatter(effectiveConfig, feedName)
66+
67+
lines
2268
.map(logLineParser.parse)
2369
.filter(logLineFilter.grep)
2470
.filter(logLineFilter.logLineQueryPredicate)
25-
.through(timestampFilter.filterTimestampAfter[F](config.timestamp.after))
2671
.through(
27-
timestampFilter.filterTimestampBefore[F](config.timestamp.before)
72+
timestampFilter.filterTimestampAfter[F](effectiveConfig.timestamp.after)
73+
)
74+
.through(
75+
timestampFilter.filterTimestampBefore[F](
76+
effectiveConfig.timestamp.before
77+
)
2878
)
2979
.map(outputLineFormatter.formatLine)
3080
.map(_.toString)
81+
}
82+
83+
def stream[F[_]: Async](config: Config): Stream[F, String] = {
84+
val topCommandsOpt: Option[List[String]] =
85+
config.configYaml.flatMap(_.commands).filter(_.nonEmpty)
86+
val feedsOpt: Option[List[Feed]] =
87+
config.configYaml.flatMap(_.feeds).filter(_.nonEmpty)
88+
89+
val finalStream = feedsOpt match {
90+
case Some(feeds) =>
91+
val feedStreams = feeds.map { feed =>
92+
val feedStream = commandsToStream[F](feed.commands)
93+
processStream(
94+
config,
95+
feedStream,
96+
feed.filter,
97+
feed.formatIn,
98+
feed.name.some
99+
)
100+
}
101+
Stream.emits(feedStreams).parJoin(feedStreams.size)
102+
103+
case None =>
104+
val baseStream = topCommandsOpt match {
105+
case Some(cmds) => commandsToStream[F](cmds)
106+
case None => stdinLinesStream[F]
107+
}
108+
processStream(config, baseStream, None, None, None)
109+
}
110+
111+
finalStream
31112
.intersperse("\n")
32113
.append(Stream.emit("\n"))
114+
}
33115
}

json-log-viewer/shared/src/main/scala/ru/d10xa/jsonlogviewer/decline/Config.scala

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import ru.d10xa.jsonlogviewer.decline.Config
44
import ru.d10xa.jsonlogviewer.decline.Config.ConfigGrep
55
import ru.d10xa.jsonlogviewer.decline.ConfigFile
66
import ru.d10xa.jsonlogviewer.decline.TimestampConfig
7+
import ru.d10xa.jsonlogviewer.decline.yaml.ConfigYaml
78
import ru.d10xa.jsonlogviewer.query.QueryAST
89

910
import scala.util.matching.Regex

json-log-viewer/shared/src/main/scala/ru/d10xa/jsonlogviewer/decline/ConfigYaml.scala

Lines changed: 0 additions & 12 deletions
This file was deleted.

json-log-viewer/shared/src/main/scala/ru/d10xa/jsonlogviewer/decline/ConfigYamlLoader.scala

Lines changed: 0 additions & 82 deletions
This file was deleted.
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package ru.d10xa.jsonlogviewer.decline.yaml
2+
3+
import ru.d10xa.jsonlogviewer.decline.Config
4+
import ru.d10xa.jsonlogviewer.decline.yaml.ConfigYaml
5+
import ru.d10xa.jsonlogviewer.query.QueryAST
6+
7+
case class ConfigYaml(
8+
filter: Option[QueryAST],
9+
formatIn: Option[Config.FormatIn],
10+
commands: Option[List[String]],
11+
feeds: Option[List[Feed]]
12+
)
13+
14+
object ConfigYaml:
15+
val empty: ConfigYaml = ConfigYaml(None, None, None, None)
16+

0 commit comments

Comments
 (0)