add reporter module, add modules table log on start

This commit is contained in:
A.C.Sukazyo Eyre 2023-12-24 22:55:24 +08:00
parent 4fb08f6240
commit c5fef1359d
Signed by: Eyre_S
GPG Key ID: C17CE40291207874
16 changed files with 148 additions and 67 deletions

View File

@ -8,7 +8,7 @@ object MornyConfiguration {
val MORNY_CODE_STORE = "https://github.com/Eyre-S/Coeur-Morny-Cono"
val MORNY_COMMIT_PATH = "https://github.com/Eyre-S/Coeur-Morny-Cono/commit/%s"
val VERSION = "2.0.0-alpha7"
val VERSION = "2.0.0-alpha8"
val VERSION_DELTA: Option[String] = None
val CODENAME = "guanggu"

View File

@ -1,12 +1,12 @@
package cc.sukazyo.cono.morny
import cc.sukazyo.cono.morny.bot.command.MornyCommandManager
import cc.sukazyo.cono.morny.daemon.MornyDaemons
import cc.sukazyo.cono.morny.Log.{exceptionLog, logger}
import cc.sukazyo.cono.morny.MornyCoeur.*
import cc.sukazyo.cono.morny.bot.api.EventListenerManager
import cc.sukazyo.cono.morny.bot.event.{MornyOnInlineQuery, MornyOnTelegramCommand, MornyOnUpdateTimestampOffsetLock}
import cc.sukazyo.cono.morny.bot.query.MornyQueryManager
import cc.sukazyo.cono.morny.reporter.MornyReport
import cc.sukazyo.cono.morny.util.schedule.Scheduler
import cc.sukazyo.cono.morny.util.EpochDateTime.EpochMillis
import cc.sukazyo.cono.morny.util.time.WatchDog
@ -111,6 +111,14 @@ class MornyCoeur (modules: List[MornyModule])(using val config: MornyConfig)(tes
given MornyCoeur = this
val externalContext: GivenContext = GivenContext()
import util.dataview.Table.format as fmtTable
logger info
s"""The following Modules have been added to current Morny:
|${fmtTable(
("Module ID" :: "Module Name" :: "Module Version" :: Nil)::Nil :::
modules.map(f => f.id :: f.name :: f.version :: Nil)
).replaceAll("\n", "\n|")}
|""".stripMargin
///>>> BLOCK START instance configure & startup stage 1
@ -156,8 +164,6 @@ class MornyCoeur (modules: List[MornyModule])(using val config: MornyConfig)(tes
/** current Morny's [[MornyTrusted]] instance */
val trusted: MornyTrusted = MornyTrusted()
val daemons: MornyDaemons = MornyDaemons()
initializeContext << daemons
val eventManager: EventListenerManager = EventListenerManager()
val commands: MornyCommandManager = MornyCommandManager()
val queries: MornyQueryManager = MornyQueryManager()
@ -203,7 +209,6 @@ class MornyCoeur (modules: List[MornyModule])(using val config: MornyConfig)(tes
eventManager, commands, queries,
initializeContext)))
eventManager register daemons.reporter.EventStatistics.EventInfoCatcher
val watchDog: WatchDog = WatchDog("watch-dog", 1000, 1500, { (consumed, _) =>
import cc.sukazyo.cono.morny.util.CommonFormat.formatDuration as f
logger warn
@ -234,7 +239,6 @@ class MornyCoeur (modules: List[MornyModule])(using val config: MornyConfig)(tes
modules.foreach(it => it.onStarting(OnStartingContext(
initializeContext)))
daemons.start()
logger info "start telegram event listening"
import com.pengrad.telegrambot.TelegramException
account.setUpdatesListener(eventManager, (e: TelegramException) => {
@ -254,7 +258,7 @@ class MornyCoeur (modules: List[MornyModule])(using val config: MornyConfig)(tes
| server responses:
|${GsonBuilder().setPrettyPrinting().create.toJson(e.response) indent 4}
|""".stripMargin
this.daemons.reporter.exception(e, "Failed get updates.")
externalContext.consume[MornyReport](_.exception(e, "Failed get updates."))
}
if (e.getCause != null) {
@ -278,7 +282,7 @@ class MornyCoeur (modules: List[MornyModule])(using val config: MornyConfig)(tes
logger error
s"""Failed get updates:
|${exceptionLog(e_other) indent 3}""".stripMargin
this.daemons.reporter.exception(e_other, "Failed get updates.")
externalContext.consume[MornyReport](_.exception(e_other, "Failed get updates."))
}
})
@ -291,7 +295,6 @@ class MornyCoeur (modules: List[MornyModule])(using val config: MornyConfig)(tes
logger info "resetting telegram command list"
commands.automaticTGListUpdate()
daemons.reporter.reportCoeurMornyLogin()
initializeContext = null
logger info "Coeur start complete."
@ -303,17 +306,26 @@ class MornyCoeur (modules: List[MornyModule])(using val config: MornyConfig)(tes
}
private def exitCleanup (): Unit = {
daemons.reporter.reportCoeurExit()
// Morny Exiting
modules.foreach(it => it.onExiting)
account.shutdown()
logger info "stopped bot account"
daemons.stop()
account.removeGetUpdatesListener()
logger info "stopped bot update listener"
tasks.waitForStop()
logger info s"morny tasks stopped: remains ${tasks.amount} tasks not be executed"
// Morny Exiting Post
if config.commandLogoutClear then
commands.automaticTGListRemove()
modules.foreach(it => it.onExitingPost)
account.shutdown()
logger info "stopped bot account"
// Morny Exited
modules.foreach(it => it.onExited)
logger info "done exit cleanup"
logger info "done exit cleanup\nMorny will EXIT now"
}
private def configure_exitCleanup (): Unit = {

View File

@ -20,6 +20,7 @@ trait MornyModule {
def onRoutineSavingData (using MornyCoeur): Unit = {}
def onExiting (using MornyCoeur): Unit = {}
def onExitingPost (using MornyCoeur): Unit = {}
def onExited (using MornyCoeur): Unit = {}
}

View File

@ -16,7 +16,8 @@ object ServerModulesLoader {
social_share.ModuleSocialShare(),
medication_timer.ModuleMedicationTimer(),
morny_misc.ModuleMornyMisc(),
uni_meow.ModuleUniMeow()
uni_meow.ModuleUniMeow(),
reporter.Module()
)

View File

@ -2,6 +2,7 @@ package cc.sukazyo.cono.morny.bot.api
import cc.sukazyo.cono.morny.{Log, MornyCoeur}
import cc.sukazyo.cono.morny.Log.{exceptionLog, logger}
import cc.sukazyo.cono.morny.reporter.MornyReport
import cc.sukazyo.cono.morny.util.tgapi.event.EventRuntimeException
import com.google.gson.GsonBuilder
import com.pengrad.telegrambot.model.Update
@ -87,7 +88,7 @@ class EventListenerManager (using coeur: MornyCoeur) extends UpdatesListener {
) indent 4) ++= "\n"
case _ =>
logger error errorMessage.toString
coeur.daemons.reporter.exception(e, "on event running")
coeur.externalContext.consume[MornyReport](_.exception(e, "on event running"))
}
}

View File

@ -3,6 +3,7 @@ package cc.sukazyo.cono.morny.bot.command
import cc.sukazyo.cono.morny.{MornyCoeur, MornySystem}
import cc.sukazyo.cono.morny.data.MornyInformation.*
import cc.sukazyo.cono.morny.data.TelegramStickers
import cc.sukazyo.cono.morny.reporter.MornyReport
import cc.sukazyo.cono.morny.util.CommonFormat.{formatDate, formatDuration}
import cc.sukazyo.cono.morny.util.tgapi.formatting.TelegramParseEscape.escapeHtml as h
import cc.sukazyo.cono.morny.util.tgapi.InputCommand
@ -162,13 +163,17 @@ class MornyInformation (using coeur: MornyCoeur) extends ITelegramCommand {
}
private def echoEventStatistics (using update: Update): Unit = {
coeur.externalContext >> { (reporter: MornyReport) =>
coeur.account exec SendMessage(
update.message.chat.id,
// language=html
s"""<b>Event Statistics :</b>
|in today
|${coeur.daemons.reporter.EventStatistics.eventStatisticsHTML}""".stripMargin
|${reporter.EventStatistics.eventStatisticsHTML}""".stripMargin
).parseMode(ParseMode.HTML).replyToMessageId(update.message.messageId)
} || {
echo404
}
}
private def echo404 (using event: Update): Unit =

View File

@ -3,7 +3,7 @@ import cc.sukazyo.cono.morny.bot.command.ICommandAlias.HiddenAlias
import cc.sukazyo.cono.morny.MornyCoeur
import cc.sukazyo.cono.morny.data.TelegramStickers
import cc.sukazyo.cono.morny.Log.logger
import cc.sukazyo.cono.morny.daemon.MornyReport
import cc.sukazyo.cono.morny.reporter.MornyReport
import cc.sukazyo.cono.morny.util.tgapi.InputCommand
import cc.sukazyo.cono.morny.util.tgapi.formatting.TelegramFormatter.*
import cc.sukazyo.cono.morny.util.tgapi.TelegramExtensions.Bot.exec
@ -41,7 +41,7 @@ class MornyManagers (using coeur: MornyCoeur) {
TelegramStickers ID_403
).replyToMessageId(event.message.messageId)
logger attention s"403 exit caught from user ${user toLogTag}"
coeur.daemons.reporter.unauthenticatedAction("/exit", user)
coeur.externalContext.consume[MornyReport](_.unauthenticatedAction("/exit", user))
}
@ -76,7 +76,7 @@ class MornyManagers (using coeur: MornyCoeur) {
TelegramStickers ID_403
).replyToMessageId(event.message.messageId)
logger attention s"403 save caught from user ${user toLogTag}"
coeur.daemons.reporter.unauthenticatedAction("/save", user)
coeur.externalContext.consume[MornyReport](_.unauthenticatedAction("/save", user))
}

View File

@ -1,29 +0,0 @@
package cc.sukazyo.cono.morny.daemon
import cc.sukazyo.cono.morny.Log.logger
import cc.sukazyo.cono.morny.MornyCoeur
class MornyDaemons (using val coeur: MornyCoeur) {
val reporter: MornyReport = MornyReport()
def start (): Unit = {
logger notice "ALL Morny Daemons starting..."
reporter.start()
logger notice "Morny Daemons started."
}
def stop (): Unit = {
logger notice "stopping All Morny Daemons..."
reporter.stop()
logger notice "stopped ALL Morny Daemons."
}
}

View File

@ -1,8 +1,6 @@
package cc.sukazyo.cono.morny.data
import cc.sukazyo.cono.morny.Log.{exceptionLog, logger}
import cc.sukazyo.cono.morny.MornyAssets
import cc.sukazyo.cono.morny.daemon.MornyReport
import cc.sukazyo.cono.morny.MornyAssets.AssetsException
import java.io.IOException

View File

@ -5,6 +5,7 @@ import cc.sukazyo.cono.morny.MornyCoeur
import cc.sukazyo.cono.morny.bot.command.{ICommandAlias, ITelegramCommand}
import cc.sukazyo.cono.morny.bot.command.ICommandAlias.ListedAlias
import cc.sukazyo.cono.morny.data.TelegramStickers
import cc.sukazyo.cono.morny.reporter.MornyReport
import cc.sukazyo.cono.morny.util.tgapi.InputCommand
import cc.sukazyo.cono.morny.util.CommonEncrypt
import cc.sukazyo.cono.morny.util.CommonEncrypt.*
@ -81,7 +82,7 @@ class Encryptor (using coeur: MornyCoeur) extends ITelegramCommand {
_r.document.fileName
)} catch case e: IOException =>
logger warn s"NetworkRequest error: TelegramFileAPI:\n\t${e.getMessage}"
coeur.daemons.reporter.exception(e, "NetworkRequest error: TelegramFileAPI")
coeur.externalContext.consume[MornyReport](_.exception(e, "NetworkRequest error: TelegramFileAPI"))
return
} else if ((_r ne null) && (_r.photo ne null)) {
try {
@ -102,11 +103,11 @@ class Encryptor (using coeur: MornyCoeur) extends ITelegramCommand {
case e: IOException =>
//noinspection DuplicatedCode
logger warn s"NetworkRequest error: TelegramFileAPI:\n\t${e.getMessage}"
coeur.daemons.reporter.exception(e, "NetworkRequest error: TelegramFileAPI")
coeur.externalContext.consume[MornyReport](_.exception(e, "NetworkRequest error: TelegramFileAPI"))
return
case e: IllegalArgumentException =>
logger warn s"FileProcess error: PhotoSize:\n\t${e.getMessage}"
coeur.daemons.reporter.exception(e, "FileProcess error: PhotoSize")
coeur.externalContext.consume[MornyReport](_.exception(e, "FileProcess error: PhotoSize"))
return
} else if ((_r ne null) && (_r.text ne null)) {
XText(_r.text)

View File

@ -0,0 +1,70 @@
package cc.sukazyo.cono.morny.reporter
import cc.sukazyo.cono.morny.internal.MornyInternalModule
import cc.sukazyo.cono.morny.Log.logger
import cc.sukazyo.cono.morny.MornyCoeur
class Module extends MornyInternalModule {
override val id: String = "morny.report"
override val name: String = "Morny/Coeur Reporter"
override val description: String | Null =
"""Report crucial messages to a Telegram channel.
|""".stripMargin
description.take(description.indexOf("\n"))
override def onInitializingPre (using MornyCoeur)(cxt: MornyCoeur.OnInitializingPreContext): Unit = {
import cxt.*
val instance = MornyReport()
externalContext << instance
givenCxt << instance
}
override def onInitializing (using MornyCoeur)(cxt: MornyCoeur.OnInitializingContext): Unit = {
import cxt.*
externalContext >> { (instance: MornyReport) =>
logger info "MornyReport will now collect your bot event statistics."
eventManager register instance.EventStatistics.EventInfoCatcher
} || {
logger warn "There seems no reporter instance is provided; skipped register events for it."
}
}
override def onStarting (using coeur: MornyCoeur)(cxt: MornyCoeur.OnStartingContext): Unit = {
import coeur.externalContext
externalContext >> { (instance: MornyReport) =>
instance.start()
} || {
logger warn "There seems no reporter instance is provided; skipped start it."
}
}
override def onStartingPost (using coeur: MornyCoeur)(cxt: MornyCoeur.OnStartingPostContext): Unit = {
import coeur.externalContext
externalContext >> { (instance: MornyReport) =>
instance.reportCoeurMornyLogin()
}
}
override def onExiting (using coeur: MornyCoeur): Unit = {
import coeur.externalContext
externalContext >> { (instance: MornyReport) =>
instance.stop()
} || {
logger warn "There seems no reporter instance need to be stop."
}
}
override def onExitingPost (using coeur: MornyCoeur): Unit = {
import coeur.externalContext
externalContext >> { (instance: MornyReport) =>
instance.reportCoeurExit()
}
}
}

View File

@ -1,4 +1,4 @@
package cc.sukazyo.cono.morny.daemon
package cc.sukazyo.cono.morny.reporter
import cc.sukazyo.cono.morny.{MornyCoeur, MornyConfig}
import cc.sukazyo.cono.morny.Log.{exceptionLog, logger}

View File

@ -6,6 +6,7 @@ import cc.sukazyo.cono.morny.data.TelegramStickers
import cc.sukazyo.cono.morny.social_share.event.OnGetSocial.tryFetchSocial
import cc.sukazyo.cono.morny.util.tgapi.TelegramExtensions.Bot.exec
import cc.sukazyo.cono.morny.Log.{exceptionLog, logger}
import cc.sukazyo.cono.morny.reporter.MornyReport
import cc.sukazyo.cono.morny.social_share.api.{SocialTwitterParser, SocialWeiboParser}
import cc.sukazyo.cono.morny.social_share.external.{twitter, weibo}
import cc.sukazyo.cono.morny.util.tgapi.TelegramExtensions.Message.entitiesSafe
@ -87,7 +88,7 @@ object OnGetSocial {
).replyToMessageId(replyToMessage)
logger error
"Error on requesting FixTweet API\n" + exceptionLog(e)
coeur.daemons.reporter.exception(e, "Error on requesting FixTweet API")
coeur.externalContext.consume[MornyReport](_.exception(e, "Error on requesting FixTweet API"))
def tryFetchSocialOfWeibo (url: weibo.StatusUrlInfo)(using replyChat: Long, replyToMessage: Int)(using coeur: MornyCoeur) =
import cc.sukazyo.cono.morny.social_share.external.weibo.MApi
@ -111,6 +112,6 @@ object OnGetSocial {
).replyToMessageId(replyToMessage)
logger error
"Error on requesting Weibo m.API\n" + exceptionLog(e)
coeur.daemons.reporter.exception(e, "Error on requesting Weibo m.API")
coeur.externalContext.consume[MornyReport](_.exception(e, "Error on requesting Weibo m.API"))
}

View File

@ -4,6 +4,7 @@ import cc.sukazyo.cono.morny.MornyCoeur
import cc.sukazyo.cono.morny.util.tgapi.formatting.NamingUtils.inlineQueryId
import cc.sukazyo.cono.morny.Log.{exceptionLog, logger}
import cc.sukazyo.cono.morny.bot.query.{InlineQueryUnit, ITelegramQuery}
import cc.sukazyo.cono.morny.reporter.MornyReport
import com.pengrad.telegrambot.model.Update
import com.pengrad.telegrambot.model.request.{InlineQueryResultArticle, InputTextMessageContent, ParseMode}
@ -17,7 +18,6 @@ class ShareToolBilibili (using coeur: MornyCoeur) extends ITelegramQuery {
private val ID_PREFIX_BILI_AV = "[morny/share/bili/av]"
private val ID_PREFIX_BILI_BV = "[morny/share/bili/bv]"
private val LINK_PREFIX = "https://bilibili.com/video/"
private val REGEX_BILI_VIDEO: Regex = "^(?:(?:https?://)?(?:www\\.)?bilibili\\.com(?:/s)?/video/((?:av|AV)(\\d{1,12})|(?:bv|BV)([A-HJ-NP-Za-km-z1-9]{10}))/?(\\?(?:p=(\\d+))?.*)?|(?:av|AV)(\\d{1,12})|(?:bv|BV)([A-HJ-NP-Za-km-z1-9]{10}))$"r
private val SHARE_FORMAT_HTML = "<a href='%s'>%s</a>"
override def query (event: Update): List[InlineQueryUnit[_]] | Null = {
@ -37,7 +37,7 @@ class ShareToolBilibili (using coeur: MornyCoeur) extends ITelegramQuery {
return null;
case e: IllegalStateException =>
logger error exceptionLog(e)
coeur.daemons.reporter.exception(e)
coeur.externalContext.consume[MornyReport](_.exception(e))
return null;
val av = result.av

View File

@ -0,0 +1,20 @@
package cc.sukazyo.cono.morny.util.dataview
object Table {
def format (table: Seq[Seq[Any]]): String = {
if (table.isEmpty) ""
else {
// Get column widths based on the maximum cell width in each column (+2 for a one character padding on each side)
val colWidths = table.transpose.map(_.map(cell => if (cell == null) 0 else cell.toString.length).max + 2)
// Format each row
val rows = table.map(_.zip(colWidths).map { case (item, size) => (" %-" + (size - 1) + "s").format(item) }
.mkString("|", "|", "|"))
// Formatted separator row, used to separate the header and draw table borders
val separator = colWidths.map("-" * _).mkString("+", "+", "+")
// Put the table together and return
(separator +: rows.head +: separator +: rows.tail :+ separator).mkString("\n")
}
}
}