-
Notifications
You must be signed in to change notification settings - Fork 81
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
rolling log #691
rolling log #691
Conversation
hi @SHSongs thank you for contribution, that is missing feature which will be nice to add, I appreciate yours contribution however there are few things to improve:
object FileLoggerConfig {
sealed trait FileRollingPolicy
object FileRollingPolicy {
case class TimeBasedRollingPolicy( /* some attributes related to this policy if needed */) extends FileRollingPolicy
// case class FixedWindowRollingPolicy() extends FileRollingPolicy // can be implemented later ...
}
}
then final case class FileLoggerConfig(
...
bufferedIOSize: Option[Int] = None
rollingPolicy: Option[FileRollingPolicy] = None
) then this needs to be added to FileLoggerConfig.config ...
|
core/shared/src/main/scala/zio/logging/internal/FileWriter.scala
Outdated
Show resolved
Hide resolved
I think I fixed all the code you reviewed. Thanks for your time and effort reviewed my code. |
case FileRollingPolicy.TimeBasedRollingPolicy => | ||
val newPath = makeDatePath() | ||
if (newPath != currentDestination) { | ||
println("새로운 파일 생성") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i assume this is for "Debug" purposes and it will be removed ...
bufferedIOSize match { | ||
case Some(bufferSize) => new BufferedWriter(output, bufferSize) | ||
case None => output | ||
} | ||
} | ||
private var writer: Writer = rolling match { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
in case of rollingPolicy, i think some concurrent constructs will have to be used as at same time 2 threads/fibers may try to log, and we do not want to create new writer twice ...
in addition to that, personally i would do something like
private[logging] class FileWriter(
destination: Path,
charset: Charset,
autoFlushBatchSize: Int,
bufferedIOSize: Option[Int],
rolling: Option[FileLoggerConfig.FileRollingPolicy]
) extends Writer {
private val writerProvider: WriterProvider = rolling match {
...
}
final def writeln(line: String): Unit = {
val writer = writerProvider.writer
writer.write(line)
writer.write(System.lineSeparator)
entriesWritten += 1
if (entriesWritten % autoFlushBatchSize == 0)
writer.flush()
}
...
}
private[logging] sealed trait WriterProvider {
def writer: Writer
}
private[logging] object WriterProvider {
final case class SimpleWriterProvider(
destination: Path,
charset: Charset,
bufferedIOSize: Option[Int]
) extends WriterProvider {
override val writer: Writer = {
val output = new OutputStreamWriter(new FileOutputStream(destination.toFile, true), charset)
bufferedIOSize match {
case Some(bufferSize) => new BufferedWriter(output, bufferSize)
case None => output
}
}
}
final case class TimeBasedRollingWriterProvider(
destination: Path,
charset: Charset,
bufferedIOSize: Option[Int]
) extends WriterProvider {
override def writer: Writer = ???
}
}
with such design you can isolate underlying writer problem, and it should be easier to add next policy later ...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I rewrote. Could you check this commit? 15d1768
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ok, now i think it is better, just some other problems are still remaining
in case of rollingPolicy, i think some concurrent constructs will have to be used as at same time 2 threads/fibers may try to log, and we do not want to create new writer twice ...
policy match { | ||
case FileRollingPolicy.TimeBasedRollingPolicy => | ||
val newPath = makeDatePath() | ||
if (newPath != currentDestination) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we will need more optimal comparison probably just based on currentDateTime
,
as makeDatePath
is relatively complex, to do it with every log line
we need to have logging as fast as possible, we need to avoid unnecessary memory allocations and computations
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
cb16d15
rewrote using currentDateTime. Could you check this commit?
|
||
final def close(): Unit = writerProvider.writer.close() | ||
|
||
private[logging] sealed trait WriterProvider { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
now WriterProvider
is inside FileWriter
i think you can move it out, so also it can be tested separately
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wrote, Could you check this commit? 5dd2e73
also documentation will need to be updated https://github.com/zio/zio-logging/blob/master/docs/file-logger.md |
core/shared/src/main/scala/zio/logging/internal/FileWriter.scala
Outdated
Show resolved
Hide resolved
i want to TestClock but not work. I think this part is the cause so I used if this PR is close. Could I change java time to ZIO time? |
that is one of reason why in those underlying impl. of loggers we are not using zio.* (Console, Clock ...) if it is not really needed you can create custom format using def timestamp(formatter: => DateTimeFormatter): LogFormat =
text {
val d = zio.Unsafe.unsafe { implicit u =>
Runtime.default.unsafe.run(zio.Clock.instant).getOrThrow()
}
formatter.format(d.atZone(ZoneId.systemDefault()))
} but i think in |
I updated the documentation. let me know if there is any missing information or anything that needs to be added. |
|
||
val spec: Spec[Environment, Any] = suite("WriterProvider")( | ||
test("Make path include date") { | ||
val destination = FileSystems.getDefault.getPath("/tmp/file_app.log") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
please add also test for paths like:
- /tmp/file_app
- /tmp/file.app.log
- /tmp/file.app.out.log
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I added test case
if (newTime != timeInUse) { | ||
currentWriter.close() | ||
currentWriter = makeWriter(makePath(destination, timeInUse, formatter), charset, bufferedIOSize) | ||
timeInUse = newTime |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
as i mentioned before, i think this implementation is not thread safe, some concurrency patterns should be used so only one caller will create new writer and close current one if it is needed
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do I make it like the code below? Also, could you provide an example code for concurrent patterns?
import java.util.concurrent.locks.ReentrantLock
private val lock: ReentrantLock = new ReentrantLock()
override def writer: Writer = {
val newTime = makeNewTime
if (newTime != timeInUse) {
lock.lock()
try {
currentWriter.close()
currentWriter = makeWriter(makePath(destination, timeInUse), charset, bufferedIOSize)
timeInUse = newTime
} finally {
lock.unlock()
}
}
currentWriter
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this could work
however i think time should be checked also under the lock
override def writer: Writer = {
val newTime = makeNewTime
if (newTime != timeInUse) {
lock.lock()
try {
if (newTime != timeInUse) {
currentWriter.close()
currentWriter = makeWriter(makePath(destination, newTime), charset, bufferedIOSize)
timeInUse = newTime
}
} finally {
lock.unlock()
}
}
currentWriter
}
or just
override def writer: Writer = {
val newTime = makeNewTime
lock.lock()
try {
if (newTime != timeInUse) {
currentWriter.close()
currentWriter = makeWriter(makePath(destination, newTime), charset, bufferedIOSize)
timeInUse = newTime
}
} finally {
lock.unlock()
}
currentWriter
}
i think that first version should be less locking, even if it is probably less correct
format = LogFormat.default, | ||
filter = LogFilter.logLevel(LogLevel.Debug), | ||
rollingPolicy = Some(FileRollingPolicy.TimeBasedRollingPolicy) | ||
) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this example now do not match with https://github.com/zio/zio-logging/blob/master/docs/slf4j2-bridge.md, i recommend to rollback this change to not make it complicated
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have completed rollback
@@ -84,6 +87,7 @@ object FileApp extends ZIOAppDefault { | |||
|logger { | |||
| format = "%timestamp{yyyy-MM-dd'T'HH:mm:ssZ} %fixed{7}{%level} [%fiberId] %name:%line %message %cause" | |||
| path = "file:///tmp/file_app.log" | |||
| rollingPolicy = TimeBasedRollingPolicy |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
you should modify also https://github.com/zio/zio-logging/blob/master/examples/core/src/main/scala/zio/logging/example/FileApp.scala, to match with this documentation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I applied
currentWriter | ||
} | ||
} | ||
object TimeBasedRollingWriterProvider { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
maybe you can add
val dateTimeFormatter: DateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd")
here to companion object, so it will be crated just once, and you don't have to have it like argument in makePath
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I applied
|
||
private var timeInUse = makeNewTime | ||
private var currentWriter: Writer = makeWriter(makePath(destination, timeInUse), charset, bufferedIOSize) | ||
val formatter: DateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
is this used ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not use, I removed formatter in provider class
override def writer: Writer = { | ||
val newTime = makeNewTime | ||
if (newTime != timeInUse) { | ||
lock.lock() | ||
try { | ||
val newTimeInLock = makeNewTime | ||
if (newTimeInLock != timeInUse) { | ||
currentWriter.close() | ||
currentWriter = makeWriter(makePath(destination, newTimeInLock), charset, bufferedIOSize) | ||
timeInUse = newTimeInLock | ||
} | ||
} finally { | ||
lock.unlock() | ||
} | ||
} | ||
currentWriter | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
#691 (comment) continuously..
I applied a concurrent pattern and checked the time under the lock.
After acquiring the lock, I created a new timestamp value using makeNewTime()
, because I think that if I were to use the time that was determined before acquiring the lock to create a new file, repeatedly create and close new writers.
A:
set(newTime) -> lock -> if(newTime..) -> create
B:
set(newTime) -> lock -> wait -> if(newTime..) -> create
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
override def writer: Writer = {
val newTime = makeNewTime
if (newTime != timeInUse) { // if they are same it will not go inside if, so it will not be locked
lock.lock()
try {
// considering that time is date, basically it should not happen that newTimeInLock != newTime
// otherwise it will have to wait for lock more almost one day
val newTimeInLock = makeNewTime
// considering proposed if (newTime != timeInUse) this additional check here in lock,
// this is for situation, when 2 threads at same time try to obtain lock, (after first if condition)
// one will get lock first, and this will change writer and timeInUse
// then after lock will be released, second one will get lock, and enter here
// but timeInUse and writer are already changed,
// so it should not be needed to recreate basically same thing
if (newTimeInLock != timeInUse) {
currentWriter.close()
currentWriter = makeWriter(makePath(destination, newTimeInLock), charset, bufferedIOSize)
timeInUse = newTimeInLock
}
} finally {
lock.unlock()
}
}
currentWriter
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I understand the comments (timeInUse and writer are already changed), and I rewrote code.
…to recreate basically same thing
final case class TimeBasedRollingWriterProvider( | ||
destination: Path, | ||
charset: Charset, | ||
bufferedIOSize: Option[Int] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
it will be good if we can test it
if we add here another attribute
makeNewTime: () => LocalDateTime = TimeBasedRollingWriterProvider.makeNewTime
then in WriterProviderSpec
it should be possible to do something like:
val timeRef = new AtomicReference[LocalDateTime](LocalDateTime.now())
timeRef.set(LocalDateTime.now().plusDays(1))
val testMakeNewTime: () => LocalDateTime = () => timeRef.get()
and then test it if in case of parallel execution, when WriterProvider.writer
is called multiple times with same time, if it return same writer
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wrote test code. Could you check this commit? 4fe78ca
sameWriter1 <- parallelExecution.map(_.toSet) | ||
_ = timeRef.set(LocalDateTime.now().plusDays(1)) | ||
sameWriter2 <- parallelExecution.map(_.toSet) | ||
} yield assertTrue(sameWriter1.size == 1 && sameWriter2.size == 1) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
good to add check sameWriter1 != sameWriter2
(makePath(destination4, localDateTime).toString == "/tmp/file.app.out-2023-03-21.log") | ||
) | ||
}, | ||
test("aa") { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
please add better description of test case
val parallelExecution = ZIO | ||
.foreachPar(1 to 5) { _ => | ||
ZIO.succeed(writerProvider.writer) | ||
} @@ ZIOAspect.parallel(5) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ZIOAspect.parallel(5) should not be needed
I rewrote code. Could you check this commit? 0aa8499 |
@SHSongs please run |
@justcoon I ran |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
thank you very much for contribution
@SHSongs did you accepted license/cla ? |
|
I use Slf4jBridge in my project, but zio-logging does not include rolling log function,
Therefore, I have created a simple rolling log function.
could you review the PR?