Write fatal errors to logs, view logs, clear tmp folder
This commit is contained in:
@@ -1,4 +1,5 @@
|
|||||||
import cats.effect._
|
import cats.effect._
|
||||||
|
import cats.effect.unsafe.implicits.global
|
||||||
import cats.implicits.catsSyntaxTuple4Parallel
|
import cats.implicits.catsSyntaxTuple4Parallel
|
||||||
import data.DataService
|
import data.DataService
|
||||||
import db.{DBConnection, PostgresService}
|
import db.{DBConnection, PostgresService}
|
||||||
@@ -6,6 +7,10 @@ import fetch.csv.{FetchService, FileNameService}
|
|||||||
import fetch.dmi
|
import fetch.dmi
|
||||||
import scheduler.Scheduler
|
import scheduler.Scheduler
|
||||||
import server.Server
|
import server.Server
|
||||||
|
|
||||||
|
import java.io.{BufferedWriter, FileWriter}
|
||||||
|
import java.time.LocalDateTime
|
||||||
|
import java.time.format.DateTimeFormatter
|
||||||
import scala.concurrent.duration.DurationInt
|
import scala.concurrent.duration.DurationInt
|
||||||
|
|
||||||
object Main extends IOApp {
|
object Main extends IOApp {
|
||||||
@@ -30,7 +35,7 @@ object Main extends IOApp {
|
|||||||
cleanupTask = scheduler.scheduleTask("Cleanup", List(1), dataService.deleteOldForecasts()).compile.drain
|
cleanupTask = scheduler.scheduleTask("Cleanup", List(1), dataService.deleteOldForecasts()).compile.drain
|
||||||
|
|
||||||
fetchGrib <- dmi.FetchService.of(dataService)
|
fetchGrib <- dmi.FetchService.of(dataService)
|
||||||
fetchGribTask = scheduler.scheduleTask("Fetch Grib", List(2), fetchGrib.fetchRecentForecasts()).compile.drain
|
fetchGribTask = scheduler.scheduleTask("Fetch Grib", List(3), fetchGrib.fetchRecentForecasts()).compile.drain
|
||||||
|
|
||||||
server <- Server.of(postgresService, dataService, fetchService)
|
server <- Server.of(postgresService, dataService, fetchService)
|
||||||
serverTask = server.run
|
serverTask = server.run
|
||||||
@@ -41,8 +46,32 @@ object Main extends IOApp {
|
|||||||
program.handleErrorWith { error =>
|
program.handleErrorWith { error =>
|
||||||
IO.delay {
|
IO.delay {
|
||||||
println(s"Fatal error occurred: ${error.getMessage}")
|
println(s"Fatal error occurred: ${error.getMessage}")
|
||||||
|
writeErrorToFile(error)
|
||||||
error.printStackTrace()
|
error.printStackTrace()
|
||||||
} *> IO.sleep(5.seconds) *> run(args)
|
} *> IO.sleep(5.seconds) *> run(args)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private def writeErrorToFile (error: Throwable) {
|
||||||
|
val timestamp = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd_HHmmss"))
|
||||||
|
val logPath = s"data/tmp/error_$timestamp.txt"
|
||||||
|
|
||||||
|
fileWriterResource(logPath).use { writer =>
|
||||||
|
IO(writer.write(formatError(error))) >>
|
||||||
|
IO(writer.flush())
|
||||||
|
}.unsafeRunSync()
|
||||||
|
}
|
||||||
|
|
||||||
|
private def formatError(error: Throwable): String = {
|
||||||
|
val timestamp = LocalDateTime.now().format(DateTimeFormatter.ISO_LOCAL_DATE_TIME)
|
||||||
|
val stackTrace = error.getStackTrace.mkString("\n ", "\n ", "")
|
||||||
|
s"[$timestamp] ERROR: ${error.getClass.getName}: ${error.getMessage}$stackTrace\n"
|
||||||
|
}
|
||||||
|
|
||||||
|
private def fileWriterResource(path: String): Resource[IO, BufferedWriter] =
|
||||||
|
Resource.make {
|
||||||
|
IO(new BufferedWriter(new FileWriter(path, true))) // append mode
|
||||||
|
} { writer =>
|
||||||
|
IO(writer.close()).handleErrorWith(e => IO(e.printStackTrace()))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -120,6 +120,7 @@ class DataService(log: Logger[IO]) {
|
|||||||
|
|
||||||
for {
|
for {
|
||||||
_ <- log.info("start cleanup")
|
_ <- log.info("start cleanup")
|
||||||
|
// _ <- IO.raiseError[Unit](new RuntimeException("Test error in deleteOldForecasts"))
|
||||||
fileList <- getFileList()
|
fileList <- getFileList()
|
||||||
fileDateList = fileList.flatMap(fileName =>
|
fileDateList = fileList.flatMap(fileName =>
|
||||||
getTimeFromName(fileName).map(extracted => (fileName, extracted._1))
|
getTimeFromName(fileName).map(extracted => (fileName, extracted._1))
|
||||||
@@ -137,6 +138,25 @@ class DataService(log: Logger[IO]) {
|
|||||||
} yield results
|
} yield results
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def deleteTmp(): IO[List[DeletionResult]] = {
|
||||||
|
for {
|
||||||
|
fileList <- Files[IO]
|
||||||
|
.list(Path(TMP_FOLDER))
|
||||||
|
.map(_.toString)
|
||||||
|
.map(_.replace(s"$TMP_FOLDER/", ""))
|
||||||
|
.compile.toList
|
||||||
|
|
||||||
|
results <- fileList.traverse { name =>
|
||||||
|
val path = Path(s"$TMP_FOLDER/${name}")
|
||||||
|
Files[IO].delete(path).attempt.flatMap {
|
||||||
|
case Right(_) => log.info(s"delete: $name").as(DeletionResult(name, true, None))
|
||||||
|
case Left(error) => log.error(s"Failed to delete $name: ${error.getMessage}")
|
||||||
|
.as(DeletionResult(name, false, Some(error.getMessage)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} yield results
|
||||||
|
}
|
||||||
|
|
||||||
private def getTimeFromName(filename: String): Option[(ZonedDateTime, ZonedDateTime)] = {
|
private def getTimeFromName(filename: String): Option[(ZonedDateTime, ZonedDateTime)] = {
|
||||||
Try {
|
Try {
|
||||||
val formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HHmm'Z'").withZone(ZoneId.of("UTC"))
|
val formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HHmm'Z'").withZone(ZoneId.of("UTC"))
|
||||||
|
|||||||
@@ -10,12 +10,12 @@ object DebugUtils {
|
|||||||
case class FolderStructureResponse(
|
case class FolderStructureResponse(
|
||||||
current: DirectoryStructure,
|
current: DirectoryStructure,
|
||||||
data: DirectoryStructure,
|
data: DirectoryStructure,
|
||||||
|
tmp: DirectoryStructure,
|
||||||
grib: DirectoryStructure
|
grib: DirectoryStructure
|
||||||
)
|
)
|
||||||
|
|
||||||
private def getDirectoryStructure(path: Path): IO[DirectoryStructure] = {
|
private def getDirectoryStructure(path: Path): IO[DirectoryStructure] = {
|
||||||
for {
|
for {
|
||||||
_ <- IO.println(path)
|
|
||||||
absolutePath <- IO(path.toString)
|
absolutePath <- IO(path.toString)
|
||||||
files <- Files[IO].list(path).compile.toList
|
files <- Files[IO].list(path).compile.toList
|
||||||
fileInfos <- files.traverse { filePath =>
|
fileInfos <- files.traverse { filePath =>
|
||||||
@@ -29,13 +29,15 @@ object DebugUtils {
|
|||||||
def getFolderStructure: IO[FolderStructureResponse] = {
|
def getFolderStructure: IO[FolderStructureResponse] = {
|
||||||
val currentPath = Path(".")
|
val currentPath = Path(".")
|
||||||
val dataPath = Path("data")
|
val dataPath = Path("data")
|
||||||
|
val tmpPath = Path("data/tmp")
|
||||||
val gribPath = Path("data/grib")
|
val gribPath = Path("data/grib")
|
||||||
|
|
||||||
for {
|
for {
|
||||||
current <- getDirectoryStructure(currentPath)
|
current <- getDirectoryStructure(currentPath)
|
||||||
data <- getDirectoryStructure(dataPath)
|
data <- getDirectoryStructure(dataPath)
|
||||||
|
tmp <- getDirectoryStructure(tmpPath)
|
||||||
grib <- getDirectoryStructure(gribPath)
|
grib <- getDirectoryStructure(gribPath)
|
||||||
response = FolderStructureResponse(current, data, grib)
|
response = FolderStructureResponse(current, data, tmp, grib)
|
||||||
} yield response
|
} yield response
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -7,6 +7,7 @@ import data.DataService
|
|||||||
import db.PostgresService
|
import db.PostgresService
|
||||||
import fetch.csv.FetchService
|
import fetch.csv.FetchService
|
||||||
import fetch.lvgmc
|
import fetch.lvgmc
|
||||||
|
import fs2.io.file.{Files, Path}
|
||||||
import server.ValidateRoutes.{AggFieldList, AggKey, CityList, DateTimeRange, Granularity, ValidateDate, ValidateDateTime, ValidateInt, ValidateMonths, ValidateZonedDateTime}
|
import server.ValidateRoutes.{AggFieldList, AggKey, CityList, DateTimeRange, Granularity, ValidateDate, ValidateDateTime, ValidateInt, ValidateMonths, ValidateZonedDateTime}
|
||||||
import io.circe.{Encoder, Json, Printer}
|
import io.circe.{Encoder, Json, Printer}
|
||||||
import org.http4s._
|
import org.http4s._
|
||||||
@@ -74,6 +75,10 @@ class Server(postgresService: PostgresService, dataService: DataService, fetch:
|
|||||||
case GET -> Root / "grib" / "delete-old-forecasts" =>
|
case GET -> Root / "grib" / "delete-old-forecasts" =>
|
||||||
dataService.deleteOldForecasts().flatMap(result => Ok(result.asJson))
|
dataService.deleteOldForecasts().flatMap(result => Ok(result.asJson))
|
||||||
|
|
||||||
|
// http://0.0.0.0:8080/api/debug/delete-tmp
|
||||||
|
case GET -> Root / "debug" / "delete-tmp" =>
|
||||||
|
dataService.deleteTmp().flatMap(result => Ok(result.asJson))
|
||||||
|
|
||||||
// http://0.0.0.0:8080/api/debug/time
|
// http://0.0.0.0:8080/api/debug/time
|
||||||
case GET -> Root / "debug" / "time" =>
|
case GET -> Root / "debug" / "time" =>
|
||||||
val nowUTC = ZonedDateTime.now(ZoneOffset.UTC)
|
val nowUTC = ZonedDateTime.now(ZoneOffset.UTC)
|
||||||
@@ -84,6 +89,21 @@ class Server(postgresService: PostgresService, dataService: DataService, fetch:
|
|||||||
case GET -> Root / "debug" / "folder-structure" =>
|
case GET -> Root / "debug" / "folder-structure" =>
|
||||||
DebugUtils.getFolderStructure.flatMap(response => Ok(response.asJson))
|
DebugUtils.getFolderStructure.flatMap(response => Ok(response.asJson))
|
||||||
|
|
||||||
|
// http://0.0.0.0:8080/api/debug/file/error_2025-03-16_152201.txt
|
||||||
|
case GET -> Root / "debug" / "file" / fileName =>
|
||||||
|
val filePath = Path(s"data/tmp/$fileName")
|
||||||
|
|
||||||
|
Files[IO].exists(filePath).flatMap {
|
||||||
|
case true =>
|
||||||
|
Files[IO].readUtf8(filePath)
|
||||||
|
.compile
|
||||||
|
.string
|
||||||
|
.flatMap(content => Ok(content).map(_.withContentType(`Content-Type`(MediaType.text.plain, Charset.`UTF-8`))))
|
||||||
|
.handleErrorWith(err => InternalServerError(s"Failed to read file: ${err.getMessage}"))
|
||||||
|
case false =>
|
||||||
|
NotFound(s"File not found: $fileName")
|
||||||
|
}
|
||||||
|
|
||||||
// http://0.0.0.0:8080/api/query/city/Liepāja,Rēzekne/20230414_2200-20230501_1230/hour/tempMax/max
|
// http://0.0.0.0:8080/api/query/city/Liepāja,Rēzekne/20230414_2200-20230501_1230/hour/tempMax/max
|
||||||
case GET -> Root / "query" / "city" / CityList(cities) / DateTimeRange(from, to) / Granularity(granularity) / field / AggKey(key) =>
|
case GET -> Root / "query" / "city" / CityList(cities) / DateTimeRange(from, to) / Granularity(granularity) / field / AggKey(key) =>
|
||||||
val userQuery = UserQuery(cities, field, key, granularity, from, to)
|
val userQuery = UserQuery(cities, field, key, granularity, from, to)
|
||||||
|
|||||||
Reference in New Issue
Block a user