diff --git a/ext/python-docker-client/Dockerfile b/ext/python-docker-client/Dockerfile index 8ad7a21..7b2c148 100644 --- a/ext/python-docker-client/Dockerfile +++ b/ext/python-docker-client/Dockerfile @@ -1,5 +1,5 @@ FROM python:3.10 -RUN python -m pip install aiodocker aiohttp && mkdir /app +RUN python -m pip install aiodocker==0.21.0 aiohttp==3.9.5 && mkdir /app WORKDIR /app COPY ./ ./ ENV PYTHONUNBUFFERED 1 diff --git a/src/main/java/app/controllers/other/ErrorController.java b/src/main/java/app/controllers/other/ErrorController.java new file mode 100644 index 0000000..febd964 --- /dev/null +++ b/src/main/java/app/controllers/other/ErrorController.java @@ -0,0 +1,29 @@ +package app.controllers.other; + +import app.annotations.interfaces.CheckPermitionFlag; +import app.annotations.interfaces.CheckWebAccess; +import app.annotations.interfaces.CollectStatistic; +import app.services.ErrorService; +import jakarta.servlet.http.HttpServletRequest; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.*; + +import java.util.Map; + +@RestController +@RequestMapping("/api/error") +public class ErrorController { + + @Autowired + private ErrorService errorService; + + @GetMapping("/list") + @CheckWebAccess + @CheckPermitionFlag + @CollectStatistic + public Map getError(HttpServletRequest request, + @RequestParam(defaultValue = "5") Integer limit, + @RequestParam(defaultValue = "0") Integer offset) { + return errorService.listError(offset, limit); + } +} diff --git a/src/main/java/app/entities/db/ErrorDb.java b/src/main/java/app/entities/db/ErrorDb.java new file mode 100644 index 0000000..1a99086 --- /dev/null +++ b/src/main/java/app/entities/db/ErrorDb.java @@ -0,0 +1,73 @@ +package app.entities.db; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import jakarta.servlet.http.HttpServletRequest; + +import java.sql.ResultSet; +import java.sql.SQLException; +import java.time.Instant; +import java.util.Arrays; +import java.util.stream.Collectors; + +public class ErrorDb { + private final Integer id; + private final Long timestamp; + private final String message; + private final String trace; + private final String query; + private Throwable e; + + public ErrorDb(ResultSet rs) throws SQLException { + this.id = rs.getInt("id"); + this.timestamp = rs.getTimestamp("err_timestamp").getTime(); + this.message = rs.getString("err_message"); + this.trace = rs.getString("err_trace"); + this.query = rs.getString("err_query"); + this.e = null; + } + + public ErrorDb(Throwable e, String query) { + this.id = null; + this.timestamp = Instant.now().getEpochSecond(); + this.message = e.toString(); + this.trace = Arrays.stream(e.getStackTrace()) + .map(StackTraceElement::toString) + .collect(Collectors.joining("\n")); + this.query = query; + this.e = e; + } + + public static String Request2Query(HttpServletRequest request) { + if (request == null) return null; + + StringBuilder str = new StringBuilder(); + str.append(request.getMethod()).append(" "); + str.append(request.getRequestURI()).append("?").append(request.getQueryString()); + return str.toString(); + } + + public Integer getId() { + return id; + } + + public Long getTimestamp() { + return timestamp; + } + + public String getMessage() { + return message; + } + + public String getTrace() { + return trace; + } + + public String getQuery() { + return query; + } + + @JsonIgnore + public Throwable getE() { + return e; + } +} \ No newline at end of file diff --git a/src/main/java/app/exceptions/handler/GlobalExceptionAdvice.java b/src/main/java/app/exceptions/handler/GlobalExceptionAdvice.java index 233e87c..f1a82bb 100644 --- a/src/main/java/app/exceptions/handler/GlobalExceptionAdvice.java +++ b/src/main/java/app/exceptions/handler/GlobalExceptionAdvice.java @@ -1,16 +1,35 @@ package app.exceptions.handler; import app.annotations.exceptions.*; +import app.entities.db.ErrorDb; +import app.exceptions.RestResponseStatusExceptionResolver; import app.exceptions.steam.InvalidSteamID; +import app.services.ErrorService; import app.services.ProfileService; +import jakarta.servlet.http.HttpServletRequest; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpEntity; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.ControllerAdvice; import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.context.request.WebRequest; @ControllerAdvice -public class GlobalExceptionAdvice { +public class GlobalExceptionAdvice extends RestResponseStatusExceptionResolver { + + @Autowired + private ErrorService errorService; + + @Autowired + private HttpServletRequest httpServletRequest; + + @ExceptionHandler(RuntimeException.class) + public ResponseEntity handleConflict(RuntimeException ex, WebRequest request) { + errorService.insertError(new ErrorDb(ex, ErrorDb.Request2Query(httpServletRequest))); + return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR); + } + @ExceptionHandler(NeedCookie.class) public ResponseEntity handNeedCookie() { return new ResponseEntity(HttpStatus.UNAUTHORIZED); diff --git a/src/main/java/app/services/ErrorService.java b/src/main/java/app/services/ErrorService.java new file mode 100644 index 0000000..f961233 --- /dev/null +++ b/src/main/java/app/services/ErrorService.java @@ -0,0 +1,58 @@ +package app.services; + +import app.entities.db.ErrorDb; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.stereotype.Service; + +import java.util.List; +import java.util.Map; + +/* +create table tf2_facti13.backend_errors +( + id serial, + err_timestamp timestamp default current_timestamp, + err_message varchar, + err_trace varchar, + err_query varchar +); + +alter table tf2_facti13.backend_errors + owner to sourcemod; + */ +@Service +public class ErrorService { + + @Autowired + @Qualifier("jt_rw") + private JdbcTemplate jdbcTemplate_rw; + + @Autowired + @Qualifier("jt_ro") + private JdbcTemplate jdbcTemplate_ro; + + private final Logger logger = LoggerFactory.getLogger(getClass()); + + public void insertError(ErrorDb errorDb) { + try { + jdbcTemplate_rw.update("INSERT INTO backend_errors (err_message, err_trace, err_query) VALUES (?,?,?)", + errorDb.getMessage(), errorDb.getTrace(), errorDb.getQuery()); + } catch (Exception e) { + logger.error("Cannot insert err into db", e); + } + } + + public Map listError(int offset, int limit) { + List result = jdbcTemplate_ro.query("SELECT * FROM backend_errors ORDER BY id DESC LIMIT ? OFFSET ?", + new Object[]{limit, offset}, (rs, n) -> new ErrorDb(rs)); + Long count = jdbcTemplate_ro.query("SELECT count(*) as c FROM backend_errors", (rs, n) -> rs.getLong("c")).get(0); + + return Map.of("result", result, "count", count); + } + + +}