package co.id.datacomsolusindo.ipphonebridge import kotlinx.coroutines.* import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.withLock import org.apache.logging.log4j.Level import org.apache.logging.log4j.LogManager import org.apache.logging.log4j.Logger import org.apache.logging.log4j.core.Filter import org.apache.logging.log4j.core.config.Configurator import org.apache.logging.log4j.core.config.builder.api.ConfigurationBuilderFactory import org.apache.logging.log4j.core.config.builder.api.FilterComponentBuilder import org.apache.logging.log4j.core.config.builder.api.LoggerComponentBuilder import org.apache.logging.log4j.core.config.builder.api.RootLoggerComponentBuilder import org.ini4j.Wini import org.springframework.boot.SpringApplication import org.springframework.boot.autoconfigure.SpringBootApplication import org.springframework.context.event.EventListener import org.springframework.messaging.simp.stomp.StompHeaderAccessor import org.springframework.messaging.support.GenericMessage import org.springframework.scheduling.annotation.EnableScheduling import org.springframework.stereotype.Component import org.springframework.web.socket.messaging.SessionConnectEvent import org.springframework.web.socket.messaging.SessionDisconnectEvent import org.springframework.web.socket.messaging.SessionSubscribeEvent import java.io.File import java.io.Serializable import java.time.LocalDateTime import java.time.format.DateTimeFormatter import java.util.* import kotlin.system.exitProcess import kotlin.system.measureTimeMillis @SpringBootApplication @EnableScheduling class IpPhoneBridgeApplication fun main(args: Array) { TimeZone.setDefault(TimeZone.getTimeZone("Asia/Jakarta")) val properties = Properties() val confFile = File("conf/general.ini") if (confFile.exists()) { val ini = Wini(File("conf/general.ini")) properties["server.port"] = ini.get("server", "port") } else { properties["server.port"] = System.getenv("PORT") } if (properties["server.port"] == null) { println("undefined port") exitProcess(1) } val sApp = SpringApplication(IpPhoneBridgeApplication::class.java) sApp.setDefaultProperties(properties) sApp.run(*args) LogManager.getLogger("co.id.datacomsolusindo.ipphonebridge.IpPhoneBridgeApplication").info("Bridge Start") val hostName = System.getenv("HOSTNAME") println("hostname $hostName") } class Client( val number: String, var connectAt: String, var lastRequest: String ) : Serializable { var reqSuccess = 0 var reqFailed = 0 var avgReqSuccessTime = 0.0 } object ClientHolder { private val mutexSucReq = Mutex() private val clientMap: MutableMap = mutableMapOf() private suspend fun massiveRun(action: suspend () -> Unit) { measureTimeMillis { coroutineScope { // sc launch { action() } } } } fun put(key: String, client: Client) { clientMap[key] = client } fun get() = clientMap.toMap() fun addFailedRequest(clNum: String) { runBlocking { withContext(Dispatchers.Default) { massiveRun { mutexSucReq.withLock { val cl = clientMap[clNum] if (cl != null) { cl.lastRequest = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS")) cl.reqFailed += 1 } } } } } } fun addSuccessRequest(clNum: String, duration: Double) { runBlocking { withContext(Dispatchers.Default) { massiveRun { // protect each increment with lock mutexSucReq.withLock { val cl = clientMap[clNum] if (cl != null) { cl.lastRequest = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS")) cl.avgReqSuccessTime = ((cl.avgReqSuccessTime * cl.reqSuccess) + duration) / (cl.reqSuccess + 1) cl.reqSuccess += 1 } } } } } } } @Component class WebSocketEventListener { fun logBuilder(clNum: String) { val builder = ConfigurationBuilderFactory.newConfigurationBuilder() val console = builder.newAppender("stdout", "Console") val appLog = builder.newAppender("appLog", "RollingFile") appLog.addAttribute("fileName", "log/app/app.log") appLog.addAttribute("filePattern", "log/app/\$\${date:yyyy - MM}/app-%d{yyyy-MM-dd}-%i.log.zip") val flow: FilterComponentBuilder = builder.newFilter( "MarkerFilter", Filter.Result.ACCEPT, Filter.Result.DENY ) flow.addAttribute("marker", "FLOW") console.add(flow) val standard = builder.newLayout("PatternLayout") standard.addAttribute("pattern", "%d %p [%c{1}] %m%n") val policies = builder.newLayout("Policies") val timeBasedTriggeringPolicy = builder.newLayout("TimeBasedTriggeringPolicy") val sizeBasedTriggeringPolicy = builder.newLayout("SizeBasedTriggeringPolicy") sizeBasedTriggeringPolicy.addAttribute("size", "10 MB") policies.addComponent(timeBasedTriggeringPolicy) policies.addComponent(sizeBasedTriggeringPolicy) console.add(standard) builder.add(console) appLog.add(standard) appLog.addComponent(policies) builder.add(appLog) val rootLogger: RootLoggerComponentBuilder = builder.newRootLogger(Level.INFO) rootLogger.add(builder.newAppenderRef("stdout")) builder.add(rootLogger) val logger: LoggerComponentBuilder = builder.newLogger("co.id.datacomsolusindo.ipphonebridge", Level.DEBUG) logger.add(builder.newAppenderRef("appLog")) logger.addAttribute("additivity", false) builder.add(logger) rootLogger.add(builder.newAppenderRef("appLog")) ClientHolder.get().entries.forEach { val clientLog = builder.newAppender("client-${it.key}", "RollingFile") clientLog.addAttribute("fileName", "log/${it.key}/client-${it.key}.log") clientLog.addAttribute( "filePattern", "log/${it.key}/\$\${date:yyyy - MM}/app-%d{yyyy-MM-dd}-%i.client-${it.key}.zip" ) clientLog.add(standard) clientLog.addComponent(policies) builder.add(clientLog) val loggerClient: LoggerComponentBuilder = builder.newLogger("client.${it.key}", Level.DEBUG) loggerClient.add(builder.newAppenderRef("client-${it.key}")) loggerClient.addAttribute("additivity", false) builder.add(loggerClient) rootLogger.add(builder.newAppenderRef("client-${it.key}")) } Configurator.reconfigure(builder.build()) } // @EventListener // private void handleSessionConnected(SessionConnectEvent event) { // ... // } // // @EventListener // private void handleSessionDisconnect(SessionDisconnectEvent event) { // ... // } @EventListener fun onConnect(event: SessionConnectEvent) { val accessor = StompHeaderAccessor.wrap(event.message) val sessionId = accessor.sessionId println("connect with session id $sessionId") } @EventListener fun onDisconnect(event: SessionDisconnectEvent) { println("disconnect with session id ${event.sessionId}") } @EventListener fun handleSessionSubscribeEvent(event: SessionSubscribeEvent) { val message = event.message as GenericMessage<*> val simDestination = message.headers["simpDestination"] as String? val accessor = StompHeaderAccessor.wrap(event.message) val sessionId = accessor.sessionId println("subscribe to $simDestination with session id $sessionId") if (!(simDestination!!.startsWith("/topic/healthCheck") || simDestination.startsWith("/topic/notification"))) { // do stuff val clNum = simDestination.split("/")[3] ClientHolder.put( clNum, Client( clNum, LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS")), LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS")) ) ) LogManager.getLogger(this.javaClass).info("clientConnected $simDestination") logBuilder(clNum) LogManager.getLogger("client.$clNum").info("Client $clNum Connected") } } } object AppLog { fun write(cls: Class<*>): Logger { return LogManager.getLogger(cls) } }