-
Notifications
You must be signed in to change notification settings - Fork 39
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
Support logging #86
Support logging #86
Conversation
@markaren I try slightly different technique to pass a Python object rather than the pointer to the logger function (this is a bit weird to pass an address to Python). But without success. So I build on top of your PR to add tests of the feature. They brought strange results. If I don't set The other point is the test errors. If I run all tests with all possible values for the message status, only the first status used is correct. To put it differently, if I execute the new tests separately they all pass. But running them all together makes them failed except for the two first one. Any idea how to fix the tests? And what is the correct behavior for turning on or off the log messages? |
Status seems to always have the value 0 when parameterized.. Even when doing @pytest.mark.parametrize("status", [Fmi2Status.ok, Fmi2Status.warning]) Also, |
At least what we have now is better than what we had. But I have no idea why the tests failed with the previous test setup. Also I wonder if this logging mechanism will pose problems when multiple FMUs are loaded in the same process. |
Perhaps that's why it failed. It may throw an exception on the second |
Indeed. |
@markaren thanks for looking into this. This is particularly annoying and brought back the need to better isolate multiple Python FMUs. |
I'm not sure that will help tbh. Loading shared libraries should be system wide no? Footnote: I have the same issue in Java, where I'm not even able to load the library once, as we seem to be able to do here. |
Can you report here the exception message you are seeing (I'm unable to get it)?
This sounds like a dirty hack. But if it is simple to set up, that could be a temporary solution. |
I don't know what Exception to catch so I don't have any exception output. |
Thanks for the reply. This is definitely complicated to debug.
No idea
That is the reason I think it would be better to pass directly a callback function. Unfortunately, what I have seen for Python required to define a new Python Object type. But this is complicated by the usage of the limited API. |
Looking at a357a84, I forgot but you can catch the exception and print it with: try:
self.__lib = cdll.LoadLibrary(str(library_path))
except Exception as e:
print(e) |
That's definitely possible for the Java version. I've done something similar before for JNI. Perhaps it's the solution here as well as you say. |
I tested successfully the following on Linux. The change is to use a different name for the slave class for each test (and therefore for the library). So this confirms the problem when loading twice a library with the same name and API... Could you try it on Windows? @pytest.mark.integration
@pytest.mark.parametrize("debug", [True, False])
@pytest.mark.parametrize("status", Fmi2Status)
def test_logger(tmp_path, debug, status):
fmpy = pytest.importorskip(
"fmpy", reason="fmpy is not available for testing the produced FMU"
)
name = f"PythonSlaveWithLogger{status.name}{debug}"
category = "category"
message = "log message"
slave_code = f"""from pythonfmu.fmi2slave import Fmi2Slave, Fmi2Causality, Integer, Real, Boolean, String
class {name}(Fmi2Slave):
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.realIn = 22.0
self.realOut = 0.0
self.register_variable(Real("realIn", causality=Fmi2Causality.input))
self.register_variable(Real("realOut", causality=Fmi2Causality.output))
def do_step(self, current_time, step_size):
self.log("{message}", {status}, "{category}", {debug})
return True
"""
script_file = tmp_path / "orig" / f"{name.lower()}.py"
script_file.parent.mkdir(parents=True, exist_ok=True)
script_file.write_text(slave_code)
fmu = FmuBuilder.build_FMU(script_file, dest=tmp_path)
fmu = tmp_path / f"{name}.fmu"
assert fmu.exists()
logger = MagicMock()
fmpy.simulate_fmu(
str(fmu),
stop_time=1e-3,
output_interval=1e-3,
logger=logger,
debug_logging=True
)
logger.assert_called_once_with(
logger.call_args[0][0], # Don't test the first argument
bytes(name, encoding="utf-8"),
int(status),
bytes(category, encoding="utf-8"),
bytes(message, encoding="utf-8")
) |
Works :) |
So the remaining questions:
|
I rather not as that will make some tools fail importing it. Better to just turn of logging in this case.
I think it is ok as is. A warning is printed saying logging will not work for this particular instance. |
Oh thanks for the info. At one point I had a different behavior. So indeed |
Thanks @markaren for the discussion. It is good to go for me. |
self.log("")
will always printself.log("", debug=True)
will print if FMIdebugLogging
is setCloses #35
Closes #85